- Ten application servers.
- One Postgres database.
- Each server keeps a connection pool of 10 connections under normal load and can grow to 20 under heavy load.
- Postgres is configured with
max_connections = 200.
max_connections = 500) so the app servers can open more connections. That can buy time but has serious drawbacks: each Postgres connection maps to an OS process and consumes memory even when idle. Raising max_connections can cause the database to spend more time and memory managing processes than executing queries.
max_connections, inspect what connections actually exist — often the real cause is different.
In this scenario, the autoscaler added more servers during the spike. Four extra servers were provisioned, explaining the additional connections: 4 servers × 10 normal connections = 40 extra connections, bringing the usual total to ~140. Under peak load each server’s pool can grow to 20 connections, which may exhaust the 200-connection limit.

pg_stat_activity which lists every live connection and what it’s doing. Run this as a superuser or a role with sufficient access:
idle in transaction, that is a red flag: the application opened a transaction, then waited (for an external call, user input, etc.) while still holding the DB connection. Example problematic output:
- The checkout code begins a DB transaction, performs writes, then calls an external payment provider before committing.
- The external call can be slow (tens of seconds), so the transaction holds the connection while doing no DB work.
- Many concurrent checkouts holding transactions lead to connection exhaustion.
- Terminate stuck sessions so new checkouts can proceed. Postgres will roll back open transactions safely.
- Configure an automatic timeout to prevent transactions remaining idle inside a transaction for too long. Use
idle_in_transaction_session_timeoutto kill sessions that stay idle in a transaction beyond a sensible threshold:
Setting
idle_in_transaction_session_timeout prevents long-running idle in transaction sessions from accumulating. Choose a value that accommodates legitimate long transactions for your workload, but protects the server from resource exhaustion.- Never hold a DB connection while waiting on a slow external operation. Commit and release the connection before making external network calls (e.g., payment provider). If you need to persist the external result, obtain a short-lived connection after the external call to write the result.
- PgBouncer is a lightweight connection pooler/proxy that sits between your application and Postgres.
- Applications can open many cheap client connections to PgBouncer while PgBouncer keeps a small, fixed pool of real Postgres connections.
- In transaction pooling mode, PgBouncer assigns a real Postgres connection only for the duration of a transaction; once the transaction completes the connection returns to the pool. This dramatically reduces the number of concurrent Postgres processes.

Be careful when using PgBouncer in transaction pooling mode: session-level features (temporary tables, session-local GUC settings, prepared statements) do not persist across pooled transactions. Some ORMs or application patterns require session pooling instead. Choose the pooling mode and application changes that match your workload.
Links and references
- Postgres pg_stat_activity documentation
- Postgres connection settings (
max_connections,idle_in_transaction_session_timeout) - PgBouncer documentation and pooling modes
max_connections.