Connection Pooling: PgBouncer vs Pgcat vs RDS Proxy
Connection pooling is foundational at any scale. PgBouncer is the default; alternatives have specific advantages.
Why pooling
Postgres backs every connection with an OS process. Without a pooler, app concurrency translates 1:1 into database processes; the limit hits faster than most teams expect.
- Per-connection cost. Each Postgres connection is a forked process; memory and scheduling overhead grow linearly.
- Connection ceiling. Default
max_connectionsis 100; thousands of app connections need a pooler in the middle. - With pooling. Thousands of app connections multiplexed onto dozens of DB connections; the database stays healthy.
- Failure mode. Without pooling, a traffic spike OOMs Postgres; with pooling, excess connections queue at the pooler.
Three options
- PgBouncer: classic; battle-tested; single-threaded.
- Pgcat: modern Rust rewrite; multi-threaded; load balancer features.
- RDS Proxy: AWS managed; tied to RDS.
Four-criteria comparison
The three tools occupy distinct points on the simplicity-to-feature axis. Pick by workload shape, not popularity.
- PgBouncer. Simple, battle-tested, single-threaded; fits 90% of single-cluster workloads.
- Pgcat. Modern Rust rewrite, multi-threaded, sharding-aware; less mature ops story.
- RDS Proxy. AWS managed, zero ops, RDS-only; lock-in is the trade-off.
- Pool mode. Transaction pooling is the default for all three; session pooling for prepared-statement-heavy apps.
When to migrate
Migrating poolers is rare. Each migration takes a quarter and the existing pooler usually works fine; pick concrete forcing functions.
- PgBouncer to Pgcat. Migrate when you need sharding routing, query rewriting, or PgBouncer's single thread becomes the bottleneck.
- PgBouncer to RDS Proxy. Migrate when you are fully on RDS and operational simplicity outweighs the cost premium.
- Stay on PgBouncer. Default; the proven path for most non-sharded workloads.
- Migration cost. Connection-string changes plus pool-mode validation plus quarterly soak; not a weekend project.
Antipatterns
- No pooling at scale. Postgres OOM at high connection count.
- Pooling without metrics. Pool-exhaustion incidents undiagnosed.
- RDS Proxy outside RDS. Wrong tool.
What to do this week
Three moves. (1) Apply this pattern to your most-loaded table. (2) Measure query latency / write throughput before/after. (3) Document the win and the constraint so the next refactor inherits the knowledge.