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

SetupMax concurrent appsPG connections used
Direct~20200 (saturated)
PgBouncer200+~80 average

Pitfalls

  • DISCARD ALL after each transaction in transaction mode
  • Long-running transactions block pool slots
  • Monitor pgbouncer_pools for waiting clients

database , pooling , performance · PostgreSQL , Docker

More in PostgreSQL