Connection Math
PostgreSQL max_connections is a hard ceiling. Application instances × pool size must stay below it with headroom for admin, replication, and autovacuum. Use PgBouncer (or provider pooler) so hundreds of app threads multiplex onto tens of real backends.
safe_backend_budget = max_connections - reserved
reserved ≈ 10 (superuser) + replication_slots + monitoring + admin cushion (15-30)
app_server_connections = app_instances × pool_max_per_instance
pgbouncer_server_connections = pool_size (to Postgres)
Rule: pgbouncer_pool_size ≤ safe_backend_budget × 0.8
Rule: sum(all pools' server_conn) ≤ safe_backend_budget
SHOW max_connections;
SELECT count ( * ) FROM pg_stat_activity WHERE backend_type = 'client backend' ;
When to reach for this:
"too many clients already" production errors
Kubernetes HPA increasing pod count
Sizing new RDS instance parameter group
40 API pods, each pool max 20, PgBouncer transaction mode, RDS max_connections=200.
App side (potential): 40 × 20 = 800 client connections to PgBouncer (OK)
PgBouncer default_pool_size = 40 server conns to Postgres
Reserved: 20 admin/replication/monitor
Used: 40 + 20 = 60 ≤ 200 × 0.8 = 160 ✓
# pgbouncer.ini excerpt
[databases]
orders = host =primary.internal port =5432 dbname =orders
[pgbouncer]
pool_mode = transaction
default_pool_size = 40
max_client_conn = 1000
reserve_pool_size = 5
-- Verify live usage
SELECT state , count ( * ) FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY 1 ;
SELECT setting:: int FROM pg_settings WHERE name = 'max_connections' ;
What this demonstrates:
App can open 800 logical connections while Postgres sees ~40 active backends
Reserve headroom for non-app sessions
max_client_conn on PgBouncer separate from Postgres max_connections
50 pods × 20 connections = 1000 > max_connections 200 → failure
Mode Server backends Caveat Session Up to client count Little savings Transaction Multiplexed per txn No session GUC persistence Statement Rare Breaks many ORMs
# Separate pools for api vs migrator (migrator bypasses pooler direct)
orders_api = host =primary dbname =orders pool_size =35
Migrator CI connects direct with pool_size=1 session for DDL.
ORM default pool = num_cpus × 5 per pod - explodes with HPA. Fix: cap pool (often 5-20 per pod).
PgBouncer pool_size = max_connections - no admin headroom. Fix: 60-70% of budget max.
Multiple services sharing one DSN pool - aggregate math required. Fix: chart per-service pool contribution.
RDS max_connections scales with RAM but not linearly with app pods - always pool. Fix: parameter group + PgBouncer sidecar.
Idle connections still cost memory - ~5-10 MB per backend. Fix: transaction pooling, lower idle timeout.
Serverless functions × high concurrency - Neon/Supabase pooler required. Fix: pooler URL not direct.
Alternative Use When Don't Use When PgBouncer Standard OLTP fleets Need session-scoped temp tables everywhere RDS Proxy AWS without operating pooler Non-AWS Supavisor / Neon pooler Hosted serverless Self-hosted unless you adopt their proxy pgpool-II Legacy deployments Greenfield (prefer PgBouncer)
Ideal pool per pod?
5-20 for typical API; load test; smaller if queries fast and HPA wide.
max_connections formula RDS?
Provider documents LEAST({DBInstanceClassMemory/9531392}, 5000) style - verify AWS docs for class.
Prepared statements with transaction pool?
Use ORM setting to disable server-side prepare or use session pool for that service.
Monitor pool waiting?
PgBouncer SHOW POOLS cl_waiting; alert when > 0 sustained.
Read replica connections?
Separate pool per endpoint; replicas have own max_connections.
Logical replication worker slots?
Count in reserved budget; slots use walsender connections.
Connection storm on deploy?
Stagger pod rollout; pool warm-up; avoid thundering herd on DB restart.
pg_stat_activity max?
Shows server backends; compare to PgBouncer server conn metrics for full picture.
When raise max_connections?
After pooler optimized and still backend exhaustion - prefer scale-up CPU/RAM with it.
Document math where?
Service runbook table: pods, pool, pooler size, max_connections, updated date.
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+ , PgBouncer 1.x , Patroni 3.x , and PostGIS 3.5+ .
Y29kZWd1aWRlcy5pb3xjZ2lvOTk2fDIwMjYwNw