PostgreSQL connection pooling and "too many connections"
Quick answer:
FATAL: sorry, too many clients alreadymeans you hitmax_connections. Each PostgreSQL connection is a separate backend process with real memory cost, so raising the limit usually makes things worse. The fix is a connection pooler (PgBouncer, pgcat, or a built-in pool) that multiplexes thousands of application clients onto a small pool of real database connections.
Why the limit exists: a process per connection
PostgreSQL uses a process-per-connection model. Every connection forks a dedicated backend process on the server, with its own memory for sorting, hashing, and caches. That's fine at tens of connections and expensive at thousands: the memory adds up and the OS spends more time context-switching between processes than doing query work.
max_connections caps how many can exist at once. When your app opens more, you
get:
FATAL: sorry, too many clients already
Why raising max_connections is the wrong fix
It's tempting to just bump the number. But you're not removing the cost — you're signing up for more of it. More backend processes mean more memory pressure and more scheduling overhead, and a database thrashing on 2,000 processes is slower than one comfortably serving 100. The real problem is usually that connections aren't being reused: a serverless function opens a fresh connection per invocation, or an app has no pool, so idle connections pile up.
The fix: a connection pooler
A pooler like PgBouncer sits between your application and PostgreSQL. Your app connects to the pooler (cheaply), and the pooler multiplexes those clients onto a small set of real database connections. Thousands of clients, a few dozen backends.
PgBouncer offers pooling modes, and the choice matters:
| Mode | A server connection is held… | Trade-off |
|---|---|---|
| Session | for the client's whole session | Safest; least reuse |
| Transaction | only for the duration of each transaction | Highest reuse; most common — but session-state features can break |
| Statement | only for a single statement | Most aggressive; disallows multi-statement transactions |
Transaction pooling is the workhorse — highest reuse — but session state
doesn't survive the handoff. Session-level SET, SQL PREPARE, session
advisory locks, LISTEN, WITH HOLD cursors, and session-lifetime temp tables
break; NOTIFY and ON COMMIT DROP temps are fine. Protocol-level prepared
statements work under transaction pooling since PgBouncer 1.21 if you set
max_prepared_statements. Confirm your driver, then size the pool to the
server's capacity — not your client count.
Why does AI-generated code cause connection storms?
Your AI coding agent (Claude Code, Cursor) writes code that opens a connection, runs a query, and moves on — correct in isolation. It can't see that the function runs in a serverless environment scaling to 500 concurrent instances, each opening its own connection, or that there's no pool in front of the database. "Connect and query" is locally right; the connection storm is a deployment and concurrency property the model never sees.
How do I keep connections under control?
- Put a pooler (PgBouncer) in front of PostgreSQL; point the app at the pooler.
- Prefer transaction pooling for web workloads, and verify session-state compatibility.
- Size the pool to the database, and cap the app-side pool too.
- Watch the connection count against
max_connectionsbefore it becomes an outage.
How DBGorilla helps
DBGorilla connects read-only and gives your AI coding agent (Claude Code, Cursor)
the real connection picture — how many backends are open against max_connections,
how many are idle — so it can tell you you're heading for a connection ceiling and
that pooling, not a bigger limit, is the fix. It surfaces and explains; it does
not change server configuration. Get started free →
FAQ
What does "FATAL: sorry, too many clients already" mean?
You exceeded max_connections, so PostgreSQL refused the connection — usually
because connections aren't pooled or reused.
Why not just raise max_connections? Each connection is a separate backend process with memory cost; thousands add RAM pressure and context-switching that often make performance worse. Pool instead.
Session vs transaction pooling in PgBouncer? Session keeps a server connection for the whole client session; transaction returns it after each transaction for far more reuse, but session-state features can break.
How big should the pool be? Small — sized to the server's CPU/disk, not the client count. A few dozen real connections can serve thousands of clients.