# 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 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_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 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 →](https://app.dbgorilla.com/signup)

## 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.

## Related

- [How to fix high CPU on a PostgreSQL database](https://www.dbgorilla.com/learn/postgres/how-to-fix-high-cpu-on-a-postgresql-database/)
- [How to fix idle in transaction connections in PostgreSQL](https://www.dbgorilla.com/learn/postgres/how-to-fix-idle-in-transaction-connections/)
- [How to identify slow queries in PostgreSQL](https://www.dbgorilla.com/learn/postgres/how-to-identify-slow-queries-in-postgresql/)
- Product: [DBGorilla docs](https://www.dbgorilla.com/docs/)