How it works
The explanation, with examples you can run.
Counting connections
The limit is per server. Every application instance has its own pool, and they add up:
pods x pool size per pod = connections on the database 30 x 10 = 300 (max_connections = 100: most of them fail)The error the client sees is FATAL: sorry, too many clients already. It tends to appear at the worst moments: during a deploy, when old and new pods both hold pools, or when autoscaling adds instances in a spike. Check what is connected:
SELECT usename, application_name, state, count(*)FROM pg_stat_activityGROUP BY 1, 2, 3ORDER BY 4 DESC;Raising max_connections is rarely the answer. The server can only run as many queries truly in parallel as it has cores, and thousands of mostly idle backends still cost memory and coordination. Many idle connections usually mean the pools are bigger than they need to be.
PgBouncer's pool modes
PgBouncer accepts many client connections and keeps a small number of real server connections, per database and user pair (default_pool_size, 20 by default). How long a client keeps a server connection depends on the mode:
| Mode | A client holds a server connection | Session features |
|---|---|---|
session | For as long as the client stays connected | All work |
transaction | Only for the length of each transaction | Some do not (below) |
statement | Only for one statement; multi-statement transactions are refused | Fewer still |
Transaction mode is what makes pooling pay off: a client that spends most of its time between transactions holds nothing.
[databases]shop = host=10.0.0.5 port=5432 dbname=shop [pgbouncer]pool_mode = transactiondefault_pool_size = 20max_client_conn = 2000max_prepared_statements = 200What breaks in transaction mode
Anything that relies on a session lasting beyond one transaction, because your next transaction may run on a different server connection:
SETandRESET(useSET LOCALinside the transaction instead);LISTEN;- session-level advisory locks (use
pg_advisory_xact_lock); WITH HOLDcursors and temporary tables that outlive a transaction;- SQL-level
PREPARE/DEALLOCATE.
Protocol-level prepared statements, which most drivers use, work in transaction mode from PgBouncer 1.21 when max_prepared_statements is above zero; since 1.24 it defaults to 200, so the support is on unless you turn it off.
Sizing
Start small. A pool a few times the database's core count is usually plenty, and a query waiting briefly for a pooled connection is cheaper than one fighting hundreds of others for CPU and locks. Watch the time clients wait for a connection, not just the number in use; a long wait means queries hold connections too long, often because a transaction stays open around slow application work.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.