Database Connection Churn
partialSummary
When an application repeatedly opens and closes database connections at high rate: due to missing connection pooling, misconfigured pool recycling, or serverless function architecture: the database server is overwhelmed with connection establishment overhead. PostgreSQL forks a new backend process per connection (10–50ms overhead each), and at high connection rates the server hits its max_connections limit and rejects new connections with "too many clients already", even though the database itself has idle capacity for query execution.
Description
PostgreSQL uses a process-per-connection model: each new client connection causes the postmaster to fork a new backend process. This fork takes 10–50ms on modern hardware and consumes approximately 5–10 MB of memory per connection for the process overhead. The actual query execution does not begin until the fork completes, TLS is negotiated, and authentication is verified: a total setup cost of 15–80ms per new connection.
Without connection pooling, an application handling 1,000 requests/second that opens a new connection per request generates 1,000 new connections/second. Each setup takes 15–80ms, meaning the connection setup itself becomes a latency component comparable to the query execution time. At 1,000 connections/second, PostgreSQL may have 100–500 connections in the setup phase simultaneously, consuming 500 MB to 2.5 GB of memory for process overhead alone before any query bytes are transferred.
The max_connections limit (default 100, commonly set to 200–500 in production) caps the total number of simultaneous connections. When the limit is reached, new connection attempts receive "FATAL: too many connections" and fail immediately. In a high-churn scenario, even with a max_connections of 500, the combination of active queries, connections in the setup phase, and connections in teardown phase can exceed the limit. The database appears to be at capacity even though actual query throughput is a fraction of hardware capability.
Serverless functions exacerbate this pattern. Each Lambda invocation opens its own connection on cold start and closes it when the invocation ends (or keeps it open for warm reuse, but Lambda concurrency limits mean hundreds of independent function instances may each hold their own connection). 500 concurrent Lambda invocations each with their own PostgreSQL connection consumes 500 connection slots : exceeding default max_connections on a modest RDS instance.
PgBouncer in session mode with pool_size=20 intercepts all 500 concurrent connection requests, queues or rejects those beyond pool_size, and multiplexes 20 real PostgreSQL connections. The 20 real connections are long-lived, eliminating the per-request fork overhead entirely. Query execution time decreases because the connection is ready immediately on pool acquisition.
Characteristics
Triggers
- ·Application code opens a new database connection per request without a connection pool
- ·Connection pool configuration has maximum connection lifetime set too low (e.g., 10 seconds), causing constant recycling
- ·Serverless function architecture where each function instance manages its own connection without external pooling (PgBouncer, RDS Proxy)
- ·Pool configuration uses a large min-idle and max connections creating many idle connections that count toward max_connections
Detection Signals
Mitigation Strategies
Deploy PgBouncer between the application and PostgreSQL with pool_mode=transaction and pool_size equal to 10–25% of max_connections. In transaction mode, a real PostgreSQL connection is held only for the duration of a transaction (typically <50ms). Between transactions, the connection is returned to the pool and available for other application requests. Supports thousands of client connections with as few as 20 real PostgreSQL connections. Eliminates per-request connection setup overhead entirely for application requests that use short transactions.
Use AWS RDS Proxy (or equivalent managed pooler) for Lambda and other serverless workloads. RDS Proxy maintains a warm connection pool to the RDS instance and multiplexes serverless function connections onto pooled real connections. Eliminates the per-invocation connection setup cost. RDS Proxy adds 1–2ms of proxy latency per query, which is negligible compared to the 15–80ms connection setup cost it eliminates.
As a short-term mitigation, increase max_connections on the PostgreSQL instance. Note: each connection consumes ~5 MB of memory for the backend process plus working_mem for query execution. Increasing max_connections from 100 to 500 requires an additional ~2 GB of RAM reservation. Set shared_buffers to 25% of total RAM after increasing max_connections. This buys time but does not address the root cause (connection setup overhead and process forking cost).
Recovery Steps
- 1.Check current connection count via SELECT count(*) FROM pg_stat_activity GROUP BY state
- 2.Identify connections in "idle" state that are not being reused: these indicate connection pool misconfiguration
- 3.Terminate idle connections with age >60 seconds to free up connection slots immediately
- 4.Restart application instances that are opening connections without pooling to force them to use the pool configuration
- 5.Deploy PgBouncer as an emergency measure if no pooler is in place: can be deployed without application restart
- 6.Set max_connections to 2x the current peak connection count as an immediate guard while the pooler is deployed
Estimated recovery time: 5–15 minutes for connection churn to resolve after PgBouncer is deployed and application instances are restarted to use the new endpoint. Immediate temporary relief via pg_terminate_backend on idle connections within 2–3 minutes.
Affected Systems
Patterns
Technologies
Basis
PostgreSQL process-per-connection model and connection setup overhead are precisely documented and empirically measured; max_connections default values are well-known; PgBouncer as the standard solution is established industry practice; serverless connection churn is a well-documented AWS Lambda + RDS operational hazard