Resolviendo un HikariPool exhaustion de 7 horas en producción
Anatomía de una caída: 23K errores, la cascada que ocultó la causa raíz, y la observabilidad que quedó como resultado.
Contexto
Un SaaS POS multi-sucursal corriendo en ECS Fargate (Java / Spring Boot) con RDS MySQL. Tres servicios comparten la base de datos a través de pools HikariCP: gateway, auth, cash-register.
Una tarde, el servicio cash-register dejó de aceptar requests. El patrón en logs: HikariPool-1 - Connection is not available, request timed out. Para cuando identificamos la causa, 23K errores se habían acumulado y el servicio había estado degradado por ~7 horas.
La cascada que ocultó la causa raíz
La base de datos en sí operaba normal. Las métricas RDS de CloudWatch mostraban CPU en niveles normales, conexiones muy por debajo de max_connections, y query latency sin cambios.
La capa faltante eran las métricas a nivel de connection pool. La visibilidad disponible cubría el lado de la base de datos, no el estado interno del pool de cada servicio. El síntoma (timeouts) parecía relacionado con la base de datos, pero la causa real (leaks dentro de la aplicación) no era visible desde métricas RDS.
La causa, identificada leyendo thread dumps:
- 55 servicios en el codebase usaban métodos transaccionales, varios con OSIV (Open Session In View) habilitado — el patrón de Spring que mantiene la Hibernate session abierta durante todo el request, incluyendo el render de vista.
- Varios controllers llamaban APIs externas dentro de transacciones, reteniendo conexiones por segundos durante la llamada HTTP.
- Bajo carga, el pool se drenaba más rápido de lo que las conexiones regresaban. Una vez agotado, cada nuevo request se encolaba en la wait queue hasta timeout.
El fix
- OSIV deshabilitado a nivel aplicación — las sesiones viven solo dentro de fronteras
@Transactional. - 55 servicios auditados para scope transaccional; llamadas externas movidas fuera del bloque transaccional.
- Sizing de HikariCP rebalanceado entre los tres servicios basado en perfiles reales de concurrencia (gateway 300, auth 150, worker 50 — total 500 de 2,730 max, ~18% headroom de utilización).
- Connection leak detection activado en config Hikari (
leakDetectionThreshold) para que futuros leaks loguen stack trace inmediatamente.
Resultado: connection leaks bajaron de 137/h a 21/h (-85%) en 48 horas post-deploy.
Mejoras de observabilidad
Para prevenir recurrencia y detectar patrones similares antes de que lleguen a producción, agregué:
- Metric filters de CloudWatch sobre líneas de log matcheando
HikariPool.*timed out,Connection is not available, ypool stats:— alarma configurada con rate > 5/min. - CloudWatch Insights queries guardadas como entradas de runbook nombradas (
pool-saturation,connection-leaks,slow-transactions). - 15+ nuevas CloudWatch Alarms organizadas por categoría: memory pressure, connection saturation, task health, RDS replication lag — cada una con un runbook de una línea adjunto en la descripción de la alarma.
- Container Insights habilitado en ECS Fargate para visibilidad de CPU, memoria y conexiones en tiempo real por task.
Cuando un patrón similar apareció en sandbox cinco días después durante el rollout de OSIV en otro servicio, la alarma se disparó en menos de 2 minutos y el problema se contuvo antes de tocar tráfico productivo.
Resultado
El incidente fue resuelto y los patrones que lo causaron se atendieron en las capas de aplicación y observabilidad. La visibilidad a nivel de pool existe donde no existía antes, con alarmas y runbooks en su lugar para las categorías de falla observadas durante el incidente.