Skip to content

CONCEPT Cited by 1 source

PgBouncer connection chain

Definition

The PgBouncer connection chain is the cascade of configuration caps that govern how client connections reach Postgres through PgBouncer. Each cap bounds a different dimension of the pooling geometry; together they form a funnel from many client-facing connections down to a tightly controlled number of upstream Postgres backend processes.

The chain

  1. max_client_conn (PgBouncer) — maximum number of client connections PgBouncer will accept. Connections are lightweight in PgBouncer, so this is frequently set in the thousands or tens of thousands.
  2. default_pool_size (PgBouncer) — number of server connections per (user, database) pool. With 4 users × 2 databases = 8 pools and default_pool_size=20, PgBouncer could open up to 160 connections to Postgres.
  3. max_db_connections and max_user_connections (PgBouncer) — hard caps that span all pools for a given database or user, respectively. Default is 0 (no limit). Act as safety valves to prevent pool arithmetic from exceeding Postgres limits.
  4. max_connections (Postgres) — total server connections must stay below this. "We should always keep a few available direct connections reserved for admin tasks and other emergency scenarios. We NEVER want PgBouncer to use all of the connections!" (Dicken).
  5. superuser_reserved_connections (Postgres) — explicit slot reservation for the superuser (distinct from operator discipline of leaving max_connections − max_db_connections slack).

(Source: sources/2026-04-21-planetscale-scaling-postgres-connections-with-pgbouncer.)

Why it's a chain, not a single number

Each cap bounds a different dimension:

  • max_client_connclient-facing concurrency (inbound).
  • default_pool_sizeper-(user, db) pool concurrency.
  • max_user_connectionsper-user aggregate across all their pools.
  • max_db_connectionsper-database aggregate across all users' pools.
  • max_connectionsPostgres-global (total backend processes).

A single cap at any one layer is insufficient: a user-specific cap doesn't prevent another user from exhausting the database; a database-specific cap doesn't prevent one user starving another; a pool-specific cap doesn't coordinate across pools. The chain enforces the right scope at the right layer.

The canonical invariant

num_pools × default_pool_size ≤ max_db_connections ≤ max_connections − reserved_slack

The operator's sizing task is to satisfy this inequality with the specific per-user / per-database / admin-reserve margins for their workload. See concepts/pgbouncer-pool-sizing-formula for the detailed arithmetic.

Worked examples

Dicken's three scenarios (sources/2026-04-21-planetscale-scaling-postgres-connections-with-pgbouncer) instantiate the chain:

  • PS-80 (1 vCPU, 8 GB): max_client_conn=500 → default_pool_size=30 → max_user_connections=30 / max_db_connections=40 → max_connections=50 (10-slot admin reserve).
  • M-2650 (32 vCPU, 256 GB): max_client_conn=10000 → default_pool_size=200 → max_user_connections=200 / max_db_connections=450 → max_connections=500 (50-slot admin reserve).
  • M-1280 (16 vCPU, 128 GB, 200 single-tenant DBs): max_client_conn=5000 → default_pool_size=2 → max_user_connections=2 / max_db_connections=2 → max_connections=400 (200 pools × 2 = 400 upstream, equal to max_connections).

Each scenario sets different dials as load-bearing:

  • PS-80 / M-2650: max_user_connections is load-bearing (prevents the app user from monopolising the pool).
  • M-1280: default_pool_size is load-bearing (per-pool cap of 2 with 200 pools is the only knob that keeps 200 × size ≤ 400).

Seen in

Last updated · 470 distilled / 1,213 read