正在加载,请稍候…

PgBouncer: Scaling PostgreSQL to 10,000+ Concurrent Connections

Master PgBouncer configuration for high-throughput PostgreSQL: transaction pooling, correct pool sizing, monitoring, and the production pitfalls that cause incidents.

PgBouncer: Scaling PostgreSQL to 10,000+ Connections

PostgreSQL creates an OS process per connection consuming ~5-10MB RAM each. PgBouncer multiplexes thousands of application connections onto a small pool of database connections.

The Three Pooling Modes

Session Pooling: One server connection per client session. Supports SET, advisory locks, LISTEN/NOTIFY. Least efficient. Use for legacy apps.

Transaction Pooling (Recommended): Server connection assigned only during transactions. 10-100x more efficient. Cannot use session-level features.

Statement Pooling: Connection returned after each statement. Autocommit only. Rarely used.

Production Configuration

[databases]
production = host=postgres.internal port=5432 dbname=appdb

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 5432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt

pool_mode = transaction
default_pool_size = 25       # Server connections per DB/user pair
max_client_conn = 10000      # Max application connections
reserve_pool_size = 5        # Extra connections for bursts
max_db_connections = 100     # Total server connections per DB

server_connect_timeout = 15
client_idle_timeout = 600
server_idle_timeout = 600
server_lifetime = 3600

server_reset_query = DISCARD ALL

Correct Pool Sizing

The formula: optimal connections = cpu_cores * 2 (SSD) or cpu_cores * 4 (HDD). Beyond this, performance degrades from context switching overhead.

def calculate_pool_size(max_connections=200, num_instances=2, cpu_cores=8):
    available = max_connections - 3  # Reserve for superuser
    optimal = cpu_cores * 2
    per_instance = available // num_instances
    return {
        'pool_size': min(optimal, per_instance),
        'max_clients': per_instance * 200,
        'reserve': max(5, per_instance // 5),
    }
# Example: 8 CPU, 200 max_connections, 2 instances
# Result: pool_size=16, max_clients=2800

Monitoring

psql -U pgbouncer -d pgbouncer

SHOW POOLS;
-- cl_waiting > 0: Pool exhausted! Clients are waiting
-- maxwait > 100ms: Connection latency too high
-- sv_idle high: Pool may be oversized

SHOW STATS;  -- req/s, avg query time

RELOAD;            -- Apply config without restart
PAUSE production;  -- For maintenance
RESUME production;
services:
  pgbouncer-exporter:
    image: prometheuscommunity/pgbouncer-exporter:latest
    environment:
      DATA_SOURCE_NAME: "postgresql://pgbouncer:pass@localhost:5432/pgbouncer"
# Key alerts:
# cl_waiting > 0 sustained => pool exhausted
# maxwait_seconds > 0.1 => connection latency too high

Transaction Mode Limitations

# BROKEN: Prepared statements
# EXECUTE may run on different server connection than PREPARE
# Fix: conn = psycopg3.connect(dsn, prepare_threshold=None)

# BROKEN: SET search_path = myschema
# Next query may run with default search_path
# Fix: ALTER USER appuser SET search_path TO myschema;

# BROKEN: Advisory locks (pg_advisory_lock/unlock on different connections)
# Fix: Use SELECT FOR UPDATE or Redis distributed locks

# BROKEN: LISTEN/NOTIFY
# Fix: Dedicated non-pooled connection for LISTEN

High Availability

backend pgbouncer_pool
    mode tcp
    balance leastconn
    server pgbouncer1 10.0.0.1:5432 check
    server pgbouncer2 10.0.0.2:5432 check
    server pgbouncer3 10.0.0.3:5432 check

3 PgBouncer instances x 25 pool size = 75 total PostgreSQL connections serving 10,000+ application connections.

Troubleshooting Guide

# "too many connections" errors
psql -c "SELECT count(*) FROM pg_stat_activity;"
# Near max_connections: reduce pool size or add instances

# Slow queries after switching to PgBouncer
psql -c "SHOW search_path;"
# Missing session config: use ALTER USER/DATABASE instead of SET

# Pool exhaustion during spikes
watch -n1 'psql -U pgbouncer -d pgbouncer -c "SHOW POOLS;"'
# Increase reserve_pool_size, reduce reserve_pool_timeout

PgBouncer lets a 200-connection PostgreSQL instance serve thousands of concurrent connections—one of the highest ROI infrastructure changes available.