Skip to main content

Size a connection pool against the limit your database actually enforces.

6
10
500
15
Connections demanded
60
12% of the limit (485)
Comfortable
Headroom: 425During a rolling deploy: 120 (25%)

Old and new instances overlap, so demand briefly doubles.

Sizing a connection pool for the deploy, not for the average

Connection pools rarely fail under steady load. They fail during a deploy, when the old pods have not shut down and the new ones have already opened their pools, and demand briefly doubles. This tool multiplies out what your fleet actually asks the database for, at rest and at that moment, and compares both against the limit the instance enforces.

How it works

  • Multiplies application instances by pool size, which is the number your database sees — not the pool size you configured.
  • Subtracts the connections reserved for replication and monitoring, so you compare against what is genuinely available.
  • Doubles the demand to model a rolling deploy, when old and new instances hold pools at the same time.
  • Flags three states: over the limit, tight enough that a deploy would break it, and comfortable.
usable       = max_connections - reserved
steady       = app_instances x pool_per_instance
during_deploy = steady x 2

safe when during_deploy <= usable

Worked example

A service on 40 pods, each with a pool of 20, against a 500-connection instance that reserves 15.

  1. usable = 500 - 15 = 485
  2. steady = 40 x 20 = 800 — already 165% of the limit
  3. headroom = 485 - 800 = -315, so connections are being refused right now
  4. halving the pool to 10: steady = 400, or 82% — it fits at rest
  5. but during a deploy: 400 x 2 = 800, still 165%, so deploys still break
  6. pool of 6: steady = 240 (49%), deploy = 480 against 485 — it just fits

The pool that survives a deploy is a third of the size of the one that merely survives the average. Nothing about the traffic changed between step two and step six — only the pool setting, and the willingness to size it for the worst moment rather than the typical one.

Reading the result

  • A pool of 6 per pod sounds small, and it is not. HikariCP's own guidance is that small pools outperform large ones: a pool only needs enough connections to keep the database's cores busy, and queuing briefly in the application is cheaper than queuing inside the database. Somewhere around a few connections per core, across the whole fleet, is the target.
  • Multiply by every process that opens a pool, not just the web tier. Background workers, cron jobs, migration runners, admin consoles and your own psql session all draw from the same ceiling, and they are the connections nobody counts until the ceiling is hit.
  • The doubling assumed here is the common case for a rolling deploy with a readiness gate. A deploy that replaces everything at once can spike higher; one that replaces a single pod at a time barely spikes at all. Adjust the instance count to model your actual strategy if it differs.
  • Serverless and autoscaling workloads break this arithmetic entirely, because the instance count is not yours to fix. That is the case a proxy — RDS Proxy, PgBouncer, Cloud SQL Auth Proxy — genuinely solves, by multiplexing many client connections onto few database ones.
  • Idle connections are not free. In PostgreSQL each is a backend process holding memory; in MySQL each carries per-thread buffers. A generous pool that sits mostly idle still costs you the memory it reserved.

Common questions

Why double the demand for a deploy?
Because a rolling deploy deliberately overlaps. The new instance must start, open its pool and pass its readiness check before the old one is drained, so for that window both hold full pools. If your platform replaces instances one at a time the overlap is smaller, but the failure mode is the same and worth checking against.
My pool is far below the limit and I still see connection errors. Why?
Usually something outside the pool. Look for workers, scheduled jobs and one-off scripts that open their own connections, for a migration running during the deploy, and for connections leaked by code that fails to return them. The pool is only the part of the demand you configured.
Is a bigger pool faster?
Past a point, no — it is slower. Once every database core is busy, more concurrent connections add context switching and lock contention rather than throughput. The measurable effect of an oversized pool is usually worse tail latency, not better throughput.
Where does a pooler like PgBouncer fit?
Between the two numbers. It lets a large, variable number of client connections share a small, fixed number of database connections, which is exactly what this tool shows you needing when the instance count is high or unpredictable. In transaction pooling mode it also frees connections between statements rather than holding them for the session.