Requests hang waiting for a database connection
“Under load, latency jumps to the pool timeout, the database CPU is fine, and logs show connection-acquire timeouts or "too many clients".”
ERROR CODES YOU MAY SEE
A code is a clue. Use its meaning and surrounding evidence to narrow the cause.
53300PostgreSQL SQLSTATE · standardThe server refused a new connection because no connection slots were available (too_many_connections).
Count connections per application and role in pg_stat_activity. Total pool size across every instance, worker, and function must stay below max_connections minus reserved slots.
Official reference for 53300 (opens in new tab)Code definition reviewed25P03PostgreSQL SQLSTATE · standardThe session was terminated because it sat idle inside an open transaction longer than idle_in_transaction_session_timeout.
Find the code path that opens a transaction and then awaits network calls or user input; that path also holds a pooled connection and its locks.
Official reference for 25P03 (opens in new tab)Code definition reviewed57014PostgreSQL SQLSTATE · platformThe query was canceled (query_canceled).
Read the accompanying message to identify the cancellation cause. Check timeouts and query traces; cancellation alone does not establish N+1 access.
Official reference for 57014 (opens in new tab)Code definition reviewed
THE PRINCIPLE
Connections are a budget: sum every pool across every process and keep the total below what the database can serve concurrently.
FIRST MOVES
- Graph pool waiting/idle/total counts next to pg_stat_activity grouped by state and application_name.
- Look for `idle in transaction` sessions: they hold a slot and locks while the app awaits I/O.
- Release connections in finally blocks; never hold a transaction open across HTTP calls.
- Size pools as instances × pool max ≤ max_connections minus reserved slots; set acquire, statement, and idle-in-transaction timeouts.
- For serverless or many short-lived workers, put PgBouncer (transaction mode) or RDS Proxy in front.
TOOLS: THEN → NOW
PATTERN SNAPSHOT
const pool = new Pool({ max: 10, connectionTimeoutMillis: 2_000 });
const client = await pool.connect();
try {
await client.query("BEGIN");
// Only database work here: no fetch(), no queue publish.
await client.query("COMMIT");
} catch (err) {
await client.query("ROLLBACK");
throw err;
} finally {
client.release();
}CLOSE THE AI. EXPLAIN THIS.
Why can a bigger pool make a saturated database slower instead of faster?HOW IT WORKS UNDERNEATH
RELATED SYMPTOMS
SOURCES
Guide reviewed