Skip to main content

PostgreSQL connection pooling and "too many connections"

Quick answer: FATAL: sorry, too many clients already means you hit max_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:

ModeA server connection is held…Trade-off
Sessionfor the client's whole sessionSafest; least reuse
Transactiononly for the duration of each transactionHighest reuse; most common — but session-state features can break
Statementonly for a single statementMost 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_connections before 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.