PostgreSQL Connection Pooling at Scale
PgBouncer vs built-in pooling — benchmarks, configuration, and why we run both in production.
Every .NET developer knows Npgsql connection pooling. At 200+ application instances, client-side pooling isn’t enough — you exhaust PostgreSQL’s max_connections.
Architecture
App (pool: 10) → PgBouncer (pool: 100) → PostgreSQL (max: 200)
PgBouncer Config
[databases]
app = host=postgres port=5432 dbname=app
[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
reserve_pool_size = 5
Transaction pooling works for our read-heavy workload. Avoid for apps using prepared statements or session-level settings.
Numbers
| Setup | Max concurrent apps | PG connections used |
|---|---|---|
| Direct | ~20 | 200 (saturated) |
| PgBouncer | 200+ | ~80 average |
Pitfalls
DISCARD ALLafter each transaction in transaction mode- Long-running transactions block pool slots
- Monitor
pgbouncer_poolsfor waiting clients
database , pooling , performance · PostgreSQL , Docker