Data and databases

5 min readPublished Updated

Connection Pooling: Why More Application Instances Should Not Mean More Database Chaos

More application instances can overload a shared database. Work through the total connection budget, waiting policy and the settings that matter under load.

During a traffic spike, the application launches additional instances. Compute capacity increases, yet customers start receiving database connection errors. Autoscaling did exactly what it was configured to do. Each new instance brought another set of connections to a database that everyone still shares.

A connection pool reuses open connections instead of creating one for every request. The application borrows a connection, performs its work and returns it. This reduces setup overhead and provides a place to limit competition for the database. A local pool does not automatically enforce a system-wide limit.

Count every pool that can exist at once

Consider an illustrative calculation, not a sizing recommendation. Eight application instances allow twenty connections each, giving a combined ceiling of 160. If autoscaling permits twenty instances, that ceiling becomes 400. Background workers, maintenance access and other applications need connections as well.

Not every pool opens its full allowance immediately; some create connections on demand. Still, designing protection around today's quiet connection count is unsafe. Include maximum scaling, the overlap between old and new instances during deployment, and the configuration used after a node fails.

Check what a pool setting actually limits. A library or proxy may maintain separate pools for each database and user combination. A central pooler can also have multiple instances, each capable of opening its own permitted connections. One setting does not necessarily describe the total.

Leave room for diagnosis and recovery. Administrators still need access when applications occupy the ordinary slots. PostgreSQL provides reserved connection capacity. Raising max_connections does not add processing power and increases the allocation of some server resources.

No connections available does not identify the bottleneck

The pool might be too small while the database has spare capacity. Alternatively, queries may hold connections for too long, wait on locks or fail to return them after errors. Expanding the pool in that situation allows more operations to wait on the same underlying problem.

For example, a handler opens a transaction, calls an external service and waits while retaining its connection. The database may perform no useful work for that request during the wait. Changing the operation's boundaries can release capacity, although the redesign must preserve required guarantees rather than merely improve a chart.

Measure connection-acquisition time, query-execution time and connection-hold time separately. Track active and idle connections, waiting requests, locks and acquisition failures. One broad API-latency metric cannot distinguish these causes.

Size against completed work and latency under realistic load. Additional concurrency may help initially and then produce resource contention. The number of connections the database permits is not evidence that the same number of simultaneously active queries is beneficial.

Test the error path separately: the handler fails, the caller cancels or the network disconnects. A connection must be released correctly or closed, and an unfinished transaction must not be passed to the next request. Verify the behaviour of the actual library rather than assuming every pool guarantees cleanup. Otherwise an occasional exception can gradually consume the available connections while the normal request path remains perfectly healthy.

The waiting queue needs an end

A bounded pool moves some demand into a queue. That is reasonable while waiting fits the product's response-time expectations. Unlimited waiting does not protect the customer: the request retains application resources, the customer retries, and more work accumulates.

Define an acquisition timeout, an overall operation deadline and a meaningful failure response. Avoid starting expensive work after the caller has already given up. Automatic retries need limits too, or pool exhaustion becomes the start of another wave of requests.

Background reporting and interactive customer actions may deserve different priorities. Separate queues or pools can limit the interference, but their combined allowance must still fit the database. Naming two pools does not create twice the capacity.

An external pooler changes session assumptions

A driver's built-in pool is often sufficient for a small application. A separate proxy can help when many clients maintain connections but use them in short bursts. In PgBouncer's transaction mode, a server connection is assigned for a transaction and can then serve another client.

The application cannot casually assume it will retain the same server-session state across transactions. Session locks, temporary objects and settings need attention. Prepared-query support depends on the mode, version and configuration. Check the actual driver and application against the PgBouncer compatibility table rather than relying on a blanket warning or promise.

Connection reuse needs care inside the application too. One customer's context must not leak into the next request. Pooling is not authorisation: access checks and appropriate state cleanup remain necessary regardless of how connections are assigned.

A proxy also introduces its own failures, upgrades and reconnection behaviour. Following a database restart, clients should not all reconnect without control. Test that recovery together with ordinary traffic spikes; otherwise the mechanism that protects steady-state operation can overwhelm the database during recovery.

Validate the setup by reaching the maximum application-instance count while some queries become slow and background work continues. Ask where waiting occurs, which operations fail and how the system recovers. Those observations connect the limits to service quality and growth rather than to a number someone once selected in a configuration file.