Postgres Connection Pool Sizer
Model your Postgres connection budget. Inputs on the left, math on the right. Every formula is shown and cited — change an input and watch the failure mode light up.
Primary database
Postgres server setting. Default: 100. Raising it costs memory (~10 MB per backend).
Pods, VMs, or concurrency caps. Each opens its own pool.
Typical ORM defaults: pg.Pool max=10, HikariCP max=10, asyncpg min=10/max=20.
Emergency psql. Leave ≥2.
Migrations, cron, jobs.
Read replicas
Model separate pool against replica endpoint.
PgBouncer
Pooler multiplexes many app connections onto fewer backends.
Result
Safe- Primary headroom
- 62
- Per-instance ceiling
- 30
38 / 100 primary connections in use (38%). Plenty of headroom.
How the math works
A Postgres connection is more expensive than most engineers expect. Each connection forks a backend process, allocates per-backend memory, and participates in locking structures that scale non-linearly with the connection count[PostgreSQL Docs, Connection Config]. The calculator above uses the same budgeting formula that production Postgres operators use in their runbooks — nothing proprietary, but laid out end-to-end.
Primary connection budget
total_primary = (app_instances × pool_size_per_instance)
+ reserved_for_admins
+ reserved_for_workers
headroom = max_connections - total_primary
utilisation = total_primary / max_connectionsThe tool classifies the result into four buckets. The thresholds are not arbitrary — they correspond to the points where different failure modes start to bite:
- Below 70% — comfortable. A deploy, a burst, a failover all fit within headroom.
- 70–89% — tight. A rolling deploy transiently doubles in-flight pool size (old pods still draining, new pods already connecting). If you're at 80%, the deploy peak is ~160% — you'll be turned away.
- 90–99% — one incident away from exhaustion. A single app restart under load is enough.
- ≥100% — you cannot actually start all your apps. The last ones will get
FATAL: sorry, too many clients already.
Why connections are expensive
Postgres uses a process-per-connection model. Each backend process carries roughly 5–10 MB of resident memory at idle, plus per-statement allocations for work_mem, temp_buffers, and sort state[PostgreSQL Docs, Resource Usage]. Locking structures also scale: the lock manager partitions its hash table across connections, and the cost of ProcArray scans grows linearly with active backends.
This is why raising max_connections to 2000 is rarely the right answer. Each additional backend consumes memory whether it's serving a query or idle, and the lock manager's scan cost compounds. A mid-sized database instance that runs fine at 200 connections can struggle at 800, even if the CPU and I/O look unchanged.
PgBouncer's multiplication effect
PgBouncer is a connection pooler that sits between your app and Postgres. When configured in transaction mode, it releases a backend back to its pool at every transaction boundary — which means a single real Postgres connection can serve dozens of short-lived app connections. The math changes fundamentally: your app side opens as many connections as it likes (to PgBouncer, which is cheap), while Postgres sees only default_pool_size backends[PgBouncer Docs].
The three pool_mode options have different trade-offs:
- transaction — the common default. Releases connections between transactions. ~20× practical multiplier (real-world measurements vary from 10× to 50× depending on transaction duration). Incompatible with session-level features: prepared statements on the app side,
LISTEN/NOTIFY, temp tables across statements,SETthat's expected to persist. - session — releases connections only at session close. Acts as a proxy, not a multiplexer. Use only when you need session features to work end-to-end.
- statement — releases connections between individual statements. Highest theoretical multiplier but breaks explicit transactions entirely. Rarely used in production.
Reserved capacity
Two reservations the calculator insists on (and warns when you skip):
- Admin connections — at least 2–3. Postgres has its own
superuser_reserved_connections(default 3) but that's for superuser access only. You want headroom for a regular DBA role topsqlin during an incident. If you're at 100% and every connection is held by an app in a timeout loop, you can't even open a shell to cancel them. - Worker connections — carve out capacity for migrations, cron, analytics workers, and scheduled jobs. These typically open outside the app pool, and their traffic is bursty at predictable times (midnight jobs, hourly rollups). Without a reservation, the nightly analytics job will crowd out user traffic at the one moment it definitely shouldn't.
When this calculator is wrong
The model is deliberately simple. It assumes steady-state. Real production has edge cases the calculator does not capture:
- Serverless / ephemeral apps — Lambda, Cloud Run, edge workers. Pool size per instance is meaningless when there are thousands of concurrent instances. Model these as (concurrent_requests × connections_per_request) against PgBouncer instead, or use a serverless-aware pooler like Neon's built-in pooler or Supavisor.
- Long-running queries — if your 95th-percentile query time is 10s and one instance runs 50 concurrent queries, the pool size math says 50 but the effective utilisation is much higher because those backends are held the whole time. A small PgBouncer pool can become a bottleneck even when the math says it shouldn't.
- Cross-region / multi-writer — the calculator assumes one primary. If you run logical replication with multiple writers, or a geo-distributed deployment, each writer is its own primary budget. Multiply accordingly.
- Managed Postgres limits — some managed providers (Neon on small tiers, Supabase below Pro) cap
max_connectionsindependently of instance class. Always verify against your provider's docs, not just the instance spec.
Further reading
- Postgres runtime-config-connection — authoritative reference for
max_connections,superuser_reserved_connections, and related settings. - PgBouncer configuration reference — full list of
pool_modesemantics and their constraints. - Postgres runtime-config-resource —
work_mem,shared_buffers, and other per-connection resource settings.
About this tool
This calculator is part of BackendBytes' reference tools collection. The math lives in an open, testable source file; read more production deep-dives for the context behind why we built this.