CONCEPT Cited by 1 source
PgBouncer pool sizing formula¶
The formula¶
The canonical PgBouncer sizing invariant is a single inequality:
Where:
num_pools= number of distinct(user, database)pairs connecting through PgBouncer. With N users and M databases where every user can connect to every database,num_pools = N × M; with 1:1 user↔database binding (single-tenant),num_pools = number_of_tenants.default_pool_size= PgBouncer's per-(user, database) pool size.max_db_connections= PgBouncer's hard cap per database across all pools.max_connections= Postgres's global connection ceiling.reserved_slack= direct-connection budget held back for admin, superuser, and emergency use (see concepts/reserved-admin-connection-budget).
(Source: sources/2026-04-21-planetscale-scaling-postgres-connections-with-pgbouncer.)
What each term constrains¶
- Left-hand side (
num_pools × default_pool_size): the theoretical maximum number of upstream Postgres connections PgBouncer could open if every pool filled to capacity simultaneously. Not usually reached, but must be bounded because any burst can reach it. - Middle (
max_db_connections): the hard cap PgBouncer enforces — even if pool arithmetic would exceed it, PgBouncer will refuse to open more. This is the safety valve against pool-arithmetic drift. - Right-hand side (
max_connections − reserved_slack): the memory-safe ceiling on the Postgres side.max_connectionsis the Postgres-global hard cap;reserved_slackis the operator's reserve for admin tasks that must bypass PgBouncer.
Inverted in single-tenant-per-database architectures¶
The typical multi-tenant intuition — "size default_pool_size for peak concurrent queries, then cap with max_db_connections" — inverts when num_pools is large.
Dicken's M-1280 scenario (200 single-tenant databases × 200 roles, 1:1 mapping):
num_pools = 200.default_pool_size = 10(innocuous-looking) would yield200 × 10 = 2,000— 5× abovemax_connections=400.- Must set
default_pool_size = 2to satisfy200 × 2 = 400 ≤ max_connections.
In the multi-tenant case, max_user_connections is the load-bearing dial (preventing one user from monopolising). In the single-tenant-per-database case, default_pool_size itself is the load-bearing dial.
Dicken's three scenarios as formula instantiations¶
| Scenario | num_pools |
default_pool_size |
Product | max_db_connections |
max_connections |
Slack |
|---|---|---|---|---|---|---|
| PS-80 | 1 × 3 = 3 | 30 | 90 | 40 | 50 | 10 |
| M-2650 | 1 × 3 = 3 | 200 | 600 | 450 | 500 | 50 |
| M-1280 | 200 × 1 = 200 | 2 | 400 | 2 | 400 | 0 (per-pool cap) |
The product column (num_pools × default_pool_size) exceeds max_db_connections in all three scenarios — that's the point. The cap arithmetic protects against pool drift; the product is theoretical maximum, not expected operating point.
The reserve discipline¶
Dicken's explicit framing: "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!" (sources/2026-04-21-planetscale-scaling-postgres-connections-with-pgbouncer).
In formula terms, reserved_slack ≥ 0 is necessary. PS-80 reserves 10 slots, M-2650 reserves 50, M-1280 reserves 0 at the PgBouncer-side cap but allows Postgres superuser_reserved_connections to carve out slots for the superuser specifically.
Why a formula helps¶
Three benefits over ad-hoc sizing:
- Detects pool-arithmetic drift: a new user / new database adds a pool; if
num_pools × default_pool_sizenow exceedsmax_db_connections, the formula fires immediately. - Enforces the admin reserve invariant: if
max_db_connections = max_connections, there's no room for direct callers; the formula surfaces this. - Makes the trade-off explicit: raising
default_pool_sizerequires either adding Postgres memory (raisingmax_connections) or reducingnum_pools. The knobs aren't independent.
Seen in¶
- sources/2026-04-21-planetscale-scaling-postgres-connections-with-pgbouncer — canonical wiki formalisation. Ben Dicken (PlanetScale, 2026-03-13) names the formula after walking through each dial in sequence: "All of this can be summarized in a nice formula."
Related¶
- concepts/pgbouncer-connection-chain — the cascade of caps the formula captures arithmetically.
- concepts/max-connections-ceiling — the right-hand-side anchor.
- concepts/reserved-admin-connection-budget — the
reserved_slackterm. - concepts/memory-overcommit-risk — the failure mode the right-hand-side protects against.
- systems/pgbouncer — the pooler whose configuration the formula governs.
- systems/postgresql — the substrate
max_connectionsanchors on. - patterns/three-pool-size-budget-allocation — the operational pattern that instantiates the formula.