Have you ever enabled HPA for a Spring Boot app on Kubernetes, only to have your database suddenly start screaming “too many connections”? It’s a classic scale-out pitfall: everything worked fine on a single instance, but the moment you added more replicas, the errors started pouring in.
This article walks through how to reverse-calculate a per-Pod maximumPoolSize from your database’s max_connections and replica count, and how to build in a safety margin for environments where HPA causes the replica count to fluctuate.
DB connection errors spike the moment you scale out replicas
A common pattern: errors suddenly spike right when HPA scales the Pod count from 2 to 5. Your application logs will show something like this.
[HikariPool-1] Connection is not available, request timed out after 30000ms.
The database side (PostgreSQL, in this case) responds with this.
FATAL: remaining connection slots are reserved for non-replication superuser connections
For MySQL, you’ll see an error like this (this article’s reverse-calculation formula and reference docs are centered on PostgreSQL, but the underlying concept applies equally to MySQL environments).
ERROR 1040 (HY000): Too many connections
What makes this tricky is that nothing looks abnormal when you check the metrics for each individual Pod. Even if you’re monitoring DB connections with a custom HealthIndicator, each Pod stays within a normal range — which makes it easy to miss that the sum across all Pods has exceeded the database’s limit.
Pool size is allocated independently per Pod
The formula “core count × 2 + 1” introduced in the HikariCP pool size tuning article is a calculation that assumes a single instance. maximumPoolSize is a per-JVM-process ceiling, and a Deployment on Kubernetes simply replicates that setting across however many Pods you’re running.
In other words, the total number of connections against the database as a whole looks like this.
Total DB connections = replica count × maximumPoolSize + connections from admin tools, monitoring, batch jobs, etc.
Scaling a Deployment from 1 to 5 implicitly means you’re also multiplying the maximum number of connections to the database by 5. If you keep tuning pool size in isolation without accounting for this, you’ll never reach the root cause.
Reverse-calculating per-Pod maximumPoolSize from max_connections
The idea is simple: choose maximumPoolSize so that it satisfies the following inequality.
replica count × maximumPoolSize + reserved connections ≤ max_connections
Reserved connections should account for monitoring tools, migration jobs, connections administrators open manually, and so on. Start by checking the current values on the DB side.
-- PostgreSQL
SHOW max_connections;
SELECT count(*) FROM pg_stat_activity;
For example, with max_connections=200, 20 reserved connections, and 5 replicas, the per-Pod ceiling works out as follows.
(200 - 20) / 5 = 36
If the resulting number is too small (say, it comes out to 5 or 6), it’s healthier to either reduce the replica count or reconsider max_connections on the DB side. Forcing the pool size down further will only increase dropped requests, so go back and rethink your capacity design from the ground up.
Building in a safety margin for HPA replica count fluctuations
If you’re running a fixed number of instances, the calculation above is sufficient. But when using HPA, the golden rule is to base the reverse calculation on maxReplicas — not the current replica count. If you base it on the current replica count, you’ll blow past the limit the moment a scale-out happens.
What makes this even trickier is the transitional period during scaling.
- During scale-out: existing Pods keep holding their connections as usual, even during the interval between a new Pod starting up and it actually establishing DB connections
- During scale-in: idle connections from Pods being terminated may not disconnect immediately and can linger for a while
To absorb this kind of fluctuation during transitions, it’s a good idea to build in roughly a 10–20% margin on top of your calculated value. That said, casually increasing maxReplicas reduces the number of connections you can allocate per Pod, so there’s a tradeoff — don’t raise it carelessly.
Implementation example in application.yml
Reflect your reverse-calculated value in application.yml, separated per environment.
spring:
datasource:
hikari:
maximum-pool-size: 30
minimum-idle: 10
connection-timeout: 20000
validation-timeout: 5000
connection-timeout is the upper limit on how long a request will wait when the pool is exhausted. Since per-Pod pools tend to be smaller in a replicated environment, they’re more prone to exhaustion — so set this value within a range that doesn’t conflict with your caller’s timeout settings. For the basics of connection configuration, also see the MySQL connection configuration guide.
Mitigating the problem with a connection pooler like PgBouncer
If reverse-calculating on the application side alone still doesn’t leave enough headroom, another option is placing a connection pooler like PgBouncer in front of the database. With PgBouncer, you can decouple the total pool size across your application from the actual number of connections to the database, keeping the DB’s real connection count within a stable range even as you scale up Pod count.
That said, if you use transaction pooling mode, be aware that things like prepared statement caching can become invalid across connections, which requires care on the HikariCP side as well. Adding PgBouncer doesn’t mean you can set maximumPoolSize without limit — treat it strictly as a mitigation, and continue to set an appropriate ceiling on the application side too.
Summary
The “too many connections” errors that start appearing after scaling out replicas on Kubernetes are, in most cases, caused by carrying over a pool size calculation that assumed a single instance.
When reviewing your setup, check through the following in order:
- Have you confirmed the DB’s max_connections?
- Did you reverse-calculate based on HPA’s
maxReplicas, not the current replica count? - Did you include reserved connections (monitoring, admin tools, etc.) in the calculation?
- Have you built in a safety margin to absorb fluctuations during transitions?
- If it’s still tight, have you considered a pooler like PgBouncer?
For basic tuning on a single instance, see the HikariCP pool size tuning guide; if you want to align application-side thread counts with pool size, check the Tomcat thread pool tuning guide; and if you’re troubleshooting connection validation errors, also take a look at HikariCP’s failed to validate connection troubleshooting guide.