← Debugging Guides

阅读中文版本

PostgreSQL — Too Many Connections

Diagnose connection exhaustion by measuring active, idle, and leaked sessions, then bound connection pools and release clients reliably.

intermediatedebuggingpostgresqldatabaseperformance

Symptoms

  • FATAL: sorry, too many clients already
  • New requests time out while the database reports connections near max_connections
  • Connection count keeps rising after load stops, indicating a leak rather than a traffic spike

Possible causes

  • Each application instance creates a large pool and the total exceeds max_connections
  • Clients are checked out but never released on error paths
  • Idle connections are held open indefinitely instead of being recycled
  • Long-running transactions or locks keep sessions active

Diagnosis steps

Count sessions by state and application

SELECT count(*), state, application_name FROM pg_stat_activity GROUP BY state, application_name ORDER BY count DESC;

Interpretation

A large idle count suggests pool sizing or leaked clients. A large active count suggests slow queries or lock waits.

Inspect the current limit and memory context

SHOW max_connections; SELECT pg_size_pretty(pg_database_size(current_database()));

Interpretation

Raising max_connections consumes per-connection memory; it is not a free fix.

Find long-running or blocked queries

SELECT pid, state, wait_event_type, now() - query_start AS age, left(query, 80) FROM pg_stat_activity WHERE state <> 'idle' ORDER BY age DESC;

Interpretation

A few long transactions can hold connections and amplify pool exhaustion.

Root cause

The total number of open sessions, including idle pools and leaked clients, reached the server's max_connections limit. New connections are rejected before authentication completes, so the application sees a connection failure rather than a slow query.

Fix

Use one bounded pool per process, set a small maximum, release clients in finally blocks, configure idle timeout and connection lifetime, and add PgBouncer or a managed pooler for many instances. Fix long transactions before increasing max_connections.

Risk note

Terminating sessions with pg_terminate_backend aborts their transactions and can leave application state inconsistent. Raise max_connections only after checking memory and connection overhead.

Verify the fix

Run a load test and watch pg_stat_activity. Idle sessions should stay bounded and return to baseline after the test. Repeated request cycles should not increase the connection count, and no new FATAL errors should appear.

Count before changing limits

Connection exhaustion is a capacity problem, but it is often caused by a leak. Start with counts by state and application name. If most sessions are idle, the pool is too large or clients are not released. If most are active, look for slow queries and lock waits.

SELECT count(*), state, application_name
FROM pg_stat_activity
GROUP BY state, application_name
ORDER BY count DESC;
SELECT pid, now() - query_start AS age, wait_event_type, left(query, 80)
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY age DESC;

Fix the pool first

const pool = new Pool({
  max: 10,
  idleTimeoutMillis: 30_000,
  connectionTimeoutMillis: 2_000,
});

async function withClient(fn) {
  const client = await pool.connect();
  try {
    return await fn(client);
  } finally {
    client.release(); // must run on every path
  }
}

A pool maximum multiplied by the number of instances must stay below the server limit, with headroom for migrations and admin sessions. PgBouncer can multiplex many clients onto fewer server connections.

Verify

  • Session count returns to baseline after load.
  • No too many clients errors under the expected concurrency.
  • Long-running queries are identified and fixed.
  • Pool metrics show bounded wait times and no leaked checkouts.

Increasing max_connections without fixing leaks only moves the failure later and increases memory pressure.