Sessions Pile Up, AAS Stays Flat: Microservices, HikariCP, and Oracle Database
A client's DBA team flagged something alarming: idle sessions on an Oracle database, climbing steadily, enough to start worrying about PROCESSES and SESSIONS limits — while Average Active Sessions (AAS) stayed low and flat the entire time. That mismatch was the confusing part: more sessions held open, but no more actual work happening. The obvious suspect was a Kubernetes-based microservices layer in front of it — thousands of Spring Boot services, each with its own HikariCP pool, each scaled by an HPA reacting to CPU. More replicas, more pools, more idle connections — case closed, right?
Not quite. The data said something more specific, and more useful.
The naive hypothesis doesn't survive contact with the data
The first instinct is usually "not enough capacity" or "too many replicas." Both are easy to check with what most teams already collect: samples of active connections, idle connections, threads waiting for a connection, and replica count, per service, every few minutes.
Two things ruled that out fast:
- Replica count barely correlated with connection waits. Across the fleet, the correlation between "threads waiting for a connection" and "number of replicas" was close to zero. Services with more pods weren't the ones queuing for connections.
- Aggregate pool utilization stayed low even during wait events. Active connections divided by total configured capacity (pool size × replica count) sat in the low single digits, on average, whether or not a service was experiencing waits that same interval. If anything, utilization during wait windows was slightly lower than during quiet ones. This wasn't a capacity shortage.
- The database's own AAS agreed. It's not just an application-side read on utilization — Oracle's Average Active Sessions, measured independently on the database itself, told the same story: no meaningful increase in real work, even as raw session count climbed steadily.
So: not too few replicas, not too small a pool, not more actual database work. Whatever was driving session growth, it wasn't the thing everyone assumed.
The floor, not the ceiling
The metric that actually correlated — strongly — was minimumIdle multiplied by replica count, against total idle connections held open. Not loosely: a clear, consistent relationship, the kind you don't get from noise.
The mechanism is simple once you see it: minimumIdle isn't a target HikariCP aims for, it's a floor it maintains and often exceeds. Set it conservatively "just in case," multiply by however many pods the HPA happens to be running this hour, and you get a standing population of idle Oracle sessions that has nothing to do with how busy the service actually is. One service, configured with a minimumIdle low enough that nobody would have flagged it in a review, was still carrying an average idle-connection count in the hundreds — aggregated across its replicas — well above what its own traffic justified.
That's also why AAS never flinched: it measures active sessions by definition, and a pile of idle ones is invisible to it almost by construction. The session count can climb for weeks and the metric everyone watches for "is the database under load" won't move at all — which is exactly why nobody caught this sooner.
Why the replica count was the wrong signal to chase
Here's the part that made this worth writing up: the HPA in question scales on CPU, which is a completely reasonable default — except CPU usage in a typical microservice isn't purely a function of database work. Business logic, serialization, framework overhead, whatever else the service does under load, all show up in the same CPU graph the HPA is watching.
That means the slope of "replicas added" tracks the slope of "CPU used," not the slope of "actual database sessions needed." A service can scale out aggressively for reasons that have nothing to do with its database access pattern, and every one of those new pods drags its full minimumIdle floor along for the ride. The database ends up paying for a scaling decision that was never really about it.
The actual lesson
None of the individual pieces here are exotic — HikariCP's idle-floor behavior is documented, HPA-on-CPU is a completely standard default. The problem was that they were tuned independently, by people who each had a reasonable default in isolation, and the interaction between them only shows up once you're looking at the database's session count and asking "where is this coming from" with actual correlation data instead of intuition.
If you're running connection-pooled services behind a CPU-based autoscaler, don't stop at "is our pool sized correctly." Ask instead: every time the autoscaler adds one more replica, what does that quietly add to a system like the database — one that scales on its own terms, not on your service's CPU graph?
Las sesiones se acumulan, el AAS no sube: microservicios, HikariCP y Oracle Database
El equipo de DBA de un cliente detectó algo preocupante: las sesiones en una base de datos Oracle no dejaban de crecer, hasta el punto de empezar a preocupar por los límites de PROCESSES y SESSIONS — mientras el Average Active Sessions (AAS) se mantenía bajo y plano todo el tiempo. Esa incoherencia era lo confuso: más sesiones abiertas, pero sin más trabajo real detrás. El sospechoso obvio era la capa de microservicios sobre Kubernetes que había delante — miles de servicios Spring Boot, cada uno con su propio pool HikariCP, cada uno escalado por un HPA que reacciona a CPU. Más réplicas, más pools, más conexiones idle — caso cerrado, ¿no?
No exactamente. Los datos decían algo más concreto, y más útil.
La hipótesis obvia no sobrevive a los datos
El primer instinto suele ser "no hay capacidad suficiente" o "hay demasiadas réplicas". Ambas cosas son fáciles de comprobar con lo que la mayoría de equipos ya recopila: muestras de conexiones activas, conexiones idle, hilos esperando conexión y número de réplicas, por servicio, cada pocos minutos.
Dos cosas descartaron esa hipótesis rápido:
- El número de réplicas apenas correlacionaba con las esperas de conexión. En todo el conjunto de servicios, la correlación entre "hilos esperando conexión" y "número de réplicas" era prácticamente cero. Los servicios con más pods no eran los que hacían cola para conseguir conexión.
- El uso agregado del pool se mantenía bajo incluso durante los eventos de espera. Conexiones activas dividido por capacidad total configurada (tamaño del pool × réplicas) se movía en un solo dígito bajo, de media, hubiera o no espera en ese mismo intervalo. Si acaso, el uso durante las ventanas con espera era ligeramente menor que durante las tranquilas. No era un problema de capacidad.
- El propio AAS de la base de datos decía lo mismo. No es solo una lectura de uso desde el lado de la aplicación — el Average Active Sessions de Oracle, medido de forma independiente en la propia base de datos, contaba la misma historia: sin aumento real de trabajo, aunque el número de sesiones seguía subiendo sin parar.
Así que: ni pocas réplicas, ni un pool demasiado pequeño, ni más trabajo real contra la base de datos. Lo que fuera que estuviera disparando el crecimiento de sesiones, no era lo que todo el mundo asumía.
El suelo, no el techo
La métrica que sí correlacionaba — y fuerte — era minimumIdle multiplicado por el número de réplicas, contra el total de conexiones idle mantenidas abiertas. No de forma vaga: una relación clara y consistente, del tipo que no sale del ruido.
El mecanismo es sencillo en cuanto lo ves: minimumIdle no es un objetivo al que aspira HikariCP, es un suelo que mantiene y a menudo supera. Lo configuras de forma conservadora "por si acaso", lo multiplicas por los pods que el HPA tenga corriendo esa hora, y obtienes una población fija de sesiones Oracle idle que no tiene nada que ver con cuánto trabajo real tiene el servicio. Un servicio, configurado con un minimumIdle lo bastante bajo como para que nadie lo hubiera marcado en una revisión, seguía manteniendo una media de conexiones idle en el orden de los cientos — agregadas entre sus réplicas — muy por encima de lo que justificaba su tráfico real.
Por eso el AAS nunca se inmutaba: por definición mide sesiones activas, y un montón de sesiones idle es casi invisible para esa métrica. El número de sesiones puede subir durante semanas y la métrica que todo el mundo vigila para saber "¿está cargada la base de datos?" no se mueve nada — justo por eso nadie lo pilló antes.
Por qué el número de réplicas era la pista equivocada
Esta es la parte que hace que merezca la pena escribirlo: el HPA en cuestión escala por CPU, que es un valor por defecto totalmente razonable — salvo que el consumo de CPU de un microservicio típico no es solo función del trabajo contra base de datos. Lógica de negocio, serialización, overhead del framework, lo que sea que el servicio haga bajo carga, todo aparece en la misma gráfica de CPU que vigila el HPA.
Eso significa que la pendiente de "réplicas añadidas" sigue la pendiente de "CPU usada", no la pendiente de "sesiones de base de datos realmente necesarias". Un servicio puede escalar agresivamente por motivos que no tienen nada que ver con su patrón de acceso a datos, y cada uno de esos pods nuevos arrastra consigo todo su suelo de minimumIdle. La base de datos acaba pagando una decisión de escalado que nunca fue realmente sobre ella.
La lección real
Ninguna de las piezas individuales aquí es exótica — el comportamiento de suelo de minimumIdle en HikariCP está documentado, un HPA que escala por CPU es un valor por defecto de lo más estándar. El problema fue que se ajustaron de forma independiente, por gente que cada una tenía un valor por defecto razonable en su propio contexto, y la interacción entre ambos solo se ve cuando miras el número de sesiones de la base de datos y preguntas "de dónde viene esto" con datos de correlación reales en vez de intuición.
Si tienes servicios con pool de conexiones detrás de un autoscaler basado en CPU, no te quedes en "¿tenemos el pool bien dimensionado?". Pregúntate mejor: cada vez que el autoscaler añade una réplica más, ¿cuánto le añade eso, en silencio, a un sistema como la base de datos — que escala por su cuenta, no según la gráfica de CPU de tu servicio?