Approaching from the Bow
← Log
2026-09-11  ·  infra  ·  9 min read

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:

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.

Idle connections stepping up and down in lockstep with replica count over a 48h window, while active connections stay flat throughout.

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:

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.

Las conexiones idle suben y bajan al mismo ritmo que el número de réplicas durante una ventana de 48h, mientras las conexiones activas se mantienen planas todo el tiempo.

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?