PostgreSQL has a connection problem. Each connection consumes roughly 5–10MB of memory on the server and requires dedicated backend processes. A db.r5.large on RDS can handle maybe 600 connections before memory pressure causes performance degradation. When you have hundreds of application servers, each with a connection pool, you hit that ceiling fast.
Connection poolers sit between your application and database, multiplexing many application connections onto fewer database connections. PgBouncer and RDS Proxy solve this problem in different ways, for different operational preferences.
Why This Problem Exists
Stateless compute scales horizontally. You run 20 application servers and each holds a pool of 25 database connections. That’s 500 connections before accounting for migrations, admin queries, and anything else touching the database. On larger fleets, it’s straightforward to saturate the connection limit of even large RDS instances.
The underlying issue is that PostgreSQL connections are expensive—each one forks a process. This is fundamentally different from databases like MySQL (which uses threads) or connection-light systems that handle thousands of connections efficiently. Until PostgreSQL’s connection model changes (there are proposals, none shipped yet), a pooler is the practical solution.
PgBouncer
PgBouncer is a single-purpose, open-source connection pooler. It’s a small C process that listens on a port, accepts connections from applications, and manages a pool of connections to the actual PostgreSQL server. Applications connect to PgBouncer as if it were PostgreSQL—the connection string is the same format, the protocol is identical.
PgBouncer supports three pooling modes:
Session pooling: A server connection is assigned for the duration of the client session. This is the safest mode—it supports all PostgreSQL features including prepared statements, SET variables, and advisory locks. It also provides the least multiplexing benefit.
Transaction pooling: A server connection is assigned only for the duration of a transaction. This is the mode most teams use. One server connection can serve many application sessions as long as they’re not in transactions simultaneously. Prepared statements and session-level SET commands don’t work in this mode.
Statement pooling: A server connection is assigned per statement. This is rarely used—it breaks anything using multi-statement transactions.
Transaction pooling with PgBouncer allows a pool of 50 server connections to serve thousands of application connections, as long as each application connection isn’t holding a transaction open for extended periods.
PgBouncer’s advantages:
- Free and open source
- Extremely lightweight (a PgBouncer instance uses ~2MB of memory)
- Battle-tested and stable (widely deployed in large PostgreSQL environments)
- Supports Aurora, RDS, Cloud SQL, self-hosted PostgreSQL—anything PostgreSQL-compatible
- Can be configured with detailed pool settings per database and user
PgBouncer’s operational requirements:
- You run it. That means deployment (typically a sidecar or a small instance/container), configuration management, high availability (running multiple PgBouncer instances behind a load balancer), monitoring, and upgrades.
- It’s a single point of failure if not deployed with redundancy.
- Authentication configuration requires some care—PgBouncer handles authentication itself and needs to know database passwords.
RDS Proxy
RDS Proxy is AWS’s managed connection pooler for RDS (MySQL, PostgreSQL, MariaDB) and Aurora. It runs as a fully managed service—you configure it through the AWS console or Terraform, point your application at the proxy endpoint, and AWS handles the pool management, failover, and scaling.
RDS Proxy’s advantages:
- Zero operational overhead: no servers to run, no HA to configure, AWS manages failover automatically
- Integrates with IAM for authentication (applications authenticate via IAM credentials rather than database passwords—strong security model)
- Automatic failover during RDS Multi-AZ events is faster through the proxy (~35 seconds) than direct connections (~100+ seconds)
- Scales connection capacity automatically
- Integrates with Secrets Manager for credential rotation without application restarts
RDS Proxy’s limitations:
- Only works with RDS and Aurora (not self-hosted PostgreSQL, not Google Cloud SQL)
- Costs ~$0.015/vCPU/hour for the underlying database, charged additionally. A db.r5.large (2 vCPU) adds ~$21/month per proxy. For multiple databases, this multiplies.
- Adds latency (~1ms per query) compared to direct connection or PgBouncer
- Some PostgreSQL features behave differently through the proxy (certain
SETcommands, some pglogical configurations) - Transaction pooling mode has the same prepared statement limitations as PgBouncer
Performance Comparison
Both introduce some latency compared to direct database connections. PgBouncer, running close to the application (sidecar or same VPC), typically adds <0.5ms. RDS Proxy adds ~1ms. For most applications this is imperceptible. For extremely latency-sensitive paths (sub-millisecond query execution), the overhead is noticeable but still small relative to typical query times.
Throughput: both handle the connection multiplexing problem effectively. The limiting factor shifts from connection count to actual database throughput—which is where it should be.
Security Considerations
PgBouncer manages authentication itself. Database passwords are stored in PgBouncer’s configuration or looked up via auth_query. Password rotation requires updating PgBouncer config and signaling it to reload. Managing secrets and rotation is your responsibility.
RDS Proxy integrates with IAM and Secrets Manager natively. Applications authenticate to the proxy using IAM credentials; the proxy handles database credentials via Secrets Manager. Rotation happens without application restarts or proxy reloads. For organizations with strict credential management requirements or frequent rotation policies, RDS Proxy’s model is more robust.
When to Use PgBouncer
You’re not on AWS RDS. Running PostgreSQL on GCP, Azure, on-premise, or self-hosted—PgBouncer is the answer. RDS Proxy doesn’t exist in those environments.
Cost matters and you have operational capacity. PgBouncer adds no per-database cost. The operational overhead is real but manageable—most teams deploy it as a sidecar in Kubernetes or as a small standalone deployment.
You need transaction pooling with Aurora Serverless v1. RDS Proxy has limitations with Aurora Serverless v1 that PgBouncer doesn’t.
You want fine-grained pool configuration. PgBouncer allows different pool sizes and modes per database and user. RDS Proxy’s configuration is more coarse.
When to Use RDS Proxy
You’re on RDS or Aurora and want zero operational overhead. The managed nature is RDS Proxy’s strongest selling point. If you don’t want to run infrastructure to manage your database infrastructure, RDS Proxy is the answer.
IAM authentication and Secrets Manager integration matter. For organizations running compliance-heavy workloads (SOC 2, HIPAA, PCI) where credential management is scrutinized, RDS Proxy’s integration reduces audit surface.
Multi-AZ failover speed matters. The faster failover through RDS Proxy (~35s vs 100s+) is a real operational advantage for applications where database failover downtime is costly.
Lambda accessing RDS. This is RDS Proxy’s killer use case. Lambda functions can create thousands of concurrent connections during scaling events. RDS Proxy handles this multiplexing problem without configuration—just point your Lambda at the proxy endpoint.
The Common Pattern
For teams on AWS RDS or Aurora with Lambda functions: use RDS Proxy. The Lambda connection storm problem is real, and RDS Proxy solves it cleanly.
For teams on RDS or Aurora with containerized applications: evaluate RDS Proxy’s cost against the operational burden of running PgBouncer. For smaller teams, RDS Proxy’s simplicity is worth the cost. For teams with Platform engineering capacity, PgBouncer is often cheaper and sufficiently manageable.
For teams not on AWS: PgBouncer, full stop.
