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 is 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 is tempting to just bump the number. But you are not removing the cost, you are 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 are not 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
does not 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, and since
PgBouncer 1.24 they are enabled by default.max_prepared_statements now
defaults to 200 rather than 0. On 1.21–1.23 you have to set it yourself.
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 cannot see that the function runs in a serverless environment scaling to 500 concurrent instances, each opening its own connection, or that there is 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 are 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 are not 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.