AWS Database Blog
Understanding the differences between PgBouncer and Amazon RDS Proxy for PostgreSQL connection pooling
When hundreds of AWS Lambda invocations or microservice pods each open their own connection, PostgreSQL can run out of connections and memory long before it runs out of CPU. In this post we explain the differences between two popular connection poolers, RDS Proxy and PgBouncer. Connection pooling multiplexes many client connections onto a smaller set of PostgreSQL backends, helping prevent connection exhaustion under burst or microservices workloads. We show you how PgBouncer and Amazon RDS Proxy approach connection pooling for PostgreSQL and the trade-offs between a self-managed pooler and a fully managed service. PostgreSQL has several connection poolers. On AWS, two commonly used options are:
PgBouncer: a lightweight, open-source connection pooler you deploy and operate yourself.
Amazon RDS Proxy: a fully managed, Multi-AZ proxy service integrated with Amazon Relational Database Service (Amazon RDS) and Amazon Aurora.
Both support transaction-level multiplexing, but they differ in operational model, authentication, failover behavior, and session-state handling, particularly pinning. To help you understand which pooler fits your workload, we provide a framework and examine pinning behaviors in detail, since pinning often determines which approach is the better fit.
PgBouncer overview
PgBouncer is a single-binary, event-driven connection pooler for PostgreSQL. Its design philosophy is minimalism: it pools connections with low overhead, particularly when deployed in a sidecar configuration, meaning it runs on the same compute as your application server.
Pooling modes
PgBouncer offers three pooling modes:
- Session mode: Connections are assigned for the entire client session. Useful for legacy applications relying on session state.
- Transaction mode: Connections are assigned only for the duration of a transaction. The recommended default for modern workloads, delivering significant connection reduction.
- Statement mode: Connections are assigned per individual statement. It is incompatible with multi-statement transactions. It’s mostly targeted at PL/Proxy implementations, because of PL/Proxy’s short, self-contained transactions. These transactions route queries across sharded PostgreSQL databases while letting PgBouncer efficiently reuse backend connections between calls.
PgBouncer authenticates clients using auth_user/auth_query, auth_file, Host Based Authentication (HBA) rules, Pluggable Authentication Modules (PAM), or the Lightweight Directory Access Protocol (LDAP) since version 1.25.0. It doesn’t natively support AWS Identity and Access Management (AWS IAM) authentication. Community workarounds exist, but this remains a custom integration requiring custom credential rotation logic that you must build and maintain.
Historically, PgBouncer’s transaction mode was incompatible with prepared statements. PgBouncer 1.21 (October 2023) introduced protocol-level prepared statement support, and 1.24+ enables it by default.
Important caveats:
- This support applies only to protocol-level prepared statements (the extended query protocol: Parse, Bind, Execute, Close).
- SQL-level
PREPARE,EXECUTE, andDEALLOCATEcommands are forwarded directly to PostgreSQL and aren’t tracked by PgBouncer. They will break under transaction pooling if the client expects them to persist across transactions. - There’s overhead to caching and re-preparing statements across server connections. With PgBouncer, you can limit the number of cached prepared statements per connection.
RDS Proxy overview
Amazon RDS Proxy is a fully managed, highly available database proxy between your application and your RDS or Aurora PostgreSQL database. It’s deployed across multiple Availability Zones with no additional infrastructure for you to provision, patch, or scale. RDS Proxy operates exclusively in a mode similar to PgBouncer’s transaction-level multiplexing mode and integrates natively with AWS IAM and AWS Secrets Manager, helping you avoid managing passwords directly in application code.
During a failover event, RDS Proxy bypasses DNS propagation and routes connections directly to the new primary, reducing failover times by up to 66% compared to direct connections. When RDS Proxy detects a session state change that isn’t appropriate for other sessions, it can’t safely reuse that database connection. As a fallback, it pins the client connection to a specific database connection until the session ends.
Feature comparison
This comparison covers the dimensions that most commonly influence the decision.
| Dimension | PgBouncer | RDS Proxy |
| Management | Self-managed (deploy, patch, monitor, scale) | Fully managed by AWS |
| High Availability | Self-managed (keepalived, DNS failover, multiple instances behind a Network Load Balancer) | Built-in Multi-AZ. Automatic failover routing |
| Pooling Modes | Session, Transaction, Statement | Transaction, reverting to something equivalent to PgBouncer Session mode if pinned |
| Authentication | auth_file, auth_query, HBA, PAM, LDAP, no native AWS IAM integration |
AWS IAM end-to-end + AWS Secrets Manager (native AWS integration) |
| Failover Transparency | Manual or custom scripting required | Automatic. Bypasses DNS propagation |
| Prepared Statements (Transaction Mode) | Supported since v1.21 (protocol-level). Enabled by default in v1.24+ | Supported through the extended query protocol |
| Session State Handling | Drops state between transactions (but not by default in transaction-pooling mode) | Pins connection when session state is detected |
| Cost | Open source + infrastructure (Amazon Elastic Compute Cloud (Amazon EC2)/Amazon Elastic Kubernetes Service (Amazon EKS), operational time) | $0.015 per vCPU-hour for provisioned instances (minimum 2 vCPUs). Per Aurora Capacity Unit (ACU) hour for Aurora serverless (minimum 8 ACUs). US East pricing shown. Pricing varies by region. |
| Proxy Latency Overhead | Low overhead because of PgBouncer’s lightweight, event-driven architecture and buffered network I/O | Additional network hop through managed Multi-AZ infrastructure. Overhead depends on deployment region and instance class |
| Observability | SHOW commands, Prometheus exporters, custom dashboards | Amazon CloudWatch metrics (including DatabaseConnectionsCurrentlySessionPinned) |
| Serverless Fit | Requires separately deployed and managed infrastructure. AWS Lambda integration is application-managed. | Fully managed and AWS native. Integrates directly with AWS Lambda and pools connections across highly concurrent, short-lived invocations. |
| Connection Limits | Configurable (max_client_conn, default_pool_size) |
Managed. Pool size is a configurable percentage of the database’s max_connections (which scales with instance memory) |
| CPU Usage | PgBouncer is single-threaded, using one CPU core per instance. At sufficiently high throughput, a single instance can therefore become CPU-bound and limit proxy throughput. Multiple PgBouncer instances can be run using so_reuseport to distribute connections across CPU cores, but this adds deployment/configuration complexity. |
RDS Proxy uses a distributed internal architecture with multiple proxy nodes. AWS automatically adjusts proxy capacity and can redistribute connections across its internal infrastructure for heat management and other purposes |
Choosing between PgBouncer and RDS Proxy
The right choice comes down to your priorities around latency, cost, operational ownership, and AWS native features. Use the following guidance to match each option to your situation.
Choose PgBouncer when
- Minimizing latency is a priority: This can be achieved by removing the additional network hop by running PgBouncer next to the application.
- Cost sensitivity at scale: PgBouncer is open source software without a per-vCPU service charge, so it can be cost-effective for large fleets where you already have the operational capacity to run it. Compare the total cost of both approaches, including the compute and operational effort to self-manage PgBouncer, using RDS Proxy pricing as a reference.
- Your team has operational maturity with pooler management: This includes high availability (HA) orchestration and deployment automation. PgBouncer offers a greater number of configuration levers, and your team knows how to use them.
Choose Amazon RDS Proxy when
- You want a fully managed connection-pooling service: You don’t want to deploy, patch, monitor, scale, or design high availability for a separate PgBouncer layer. AWS manages the proxy infrastructure for you.
- You need AWS native authentication and credential management: RDS Proxy integrates with AWS Identity and Access Management (IAM) and AWS Secrets Manager, so you can use IAM-based database authentication and centrally manage database credentials. With a self-managed PgBouncer deployment, you would need to implement and operate equivalent mechanisms yourself.
- You want AWS managed failover with reduced DNS/reconnection impact: RDS Proxy provides a stable endpoint for applications and manages connections to the underlying database during failover. This can reduce the impact of DNS propagation and the need for applications to independently rediscover and reconnect to the new database instance. With PgBouncer, similar behavior can be engineered, but you must design and operate the HA and failover mechanism yourself.
- AWS native monitoring and management: RDS Proxy integrates with Amazon CloudWatch and the RDS console, giving you AWS native metrics and operational visibility for the proxy and its connection pools. With PgBouncer, you would build and maintain equivalent monitoring yourself.
Understanding pinning behavior
Pinning determines how much connection reuse you actually get with RDS Proxy/PgBouncer, so it’s worth understanding in detail. The following sections explain what triggers pinning.
How pinning works
In transaction-level multiplexing, the proxy assigns a backend connection only for the duration of a transaction. Pinning occurs when session state is detected that would cause incorrect behavior if the backend were reassigned. Once pinned, the connection is held exclusively, and the multiplexing benefit is lost. Pinning in RDS Proxy is caused by various actions described fully in the RDS Proxy pinning documentation. Common causes include SET commands in sessions, SQL statements with a text size above 16 KB, and explicit use of sequences for populating columns with incremental values.
Note: If a significant proportion of your connections are pinned, you are losing the benefits of connection pooling, meaning you have more sessions connected to your underlying database.
Pinning and session state leakage
Pinning isn’t a bug. It’s a safety mechanism. RDS Proxy adopts a safety-first approach. In contrast, PgBouncer transaction pooling returns the database connection to the pool at the end of each transaction, leaving the application responsible for managing session state so it doesn’t leak between clients. Session leakage is a situation where session-level state set by one client persists on a pooled database connection. That state can then inadvertently affect another client that subsequently uses the same connection. The PgBouncer documentation states that in transaction pooling mode, clients shouldn’t use session-based features. This is because in transaction pooling mode the default for server_reset_query_always is 0/off, meaning that session state can potentially leak. Only after you set this value to 1/on does PgBouncer issue a DISCARD ALL at the end of each transaction, resetting the state. The parameter is provided as a workaround for broken setups that need deterministic breakage of their session state.
The following example illustrates how a misconfiguration can lead to session leakage in PgBouncer. It uses Row Level Security (RLS) with a session variable set using current_setting(), a common pattern for passing application-level context into SQL policies without modifying every query.
The setup
Neither client A nor B bypasses Row Level Security (that is, they don’t own the table or have superuser privileges).
The leak in PgBouncer
Through PgBouncer with pool_mode = transaction and default_pool_size = 1. The pool size is deliberately set to 1 so both clients use the same backend connection for illustration.
Client A:
Client B: sets nothing, so RLS should return zero rows
The same scenario using RDS Proxy
Client A:
Client B: sets nothing, so RLS should return zero rows
The following line is also observed in the RDS Proxy CloudWatch log
In this example, Client A could have avoided the session leakage in PgBouncer by using transaction-scoped state (for example, SELECT set_config('app.tenant', 'acme', true);). With PgBouncer, correct production setups typically have the application layer establishing the context, often with SET LOCAL or set_config with the is_local parameter set to true. This example demonstrates that with PgBouncer in transaction pooling mode, you are responsible for ensuring session state is not leaked. RDS Proxy, by contrast, provides the benefits of transaction-level pooling for sessions that comply, while also keeping session-specific details specific to that session.
Mitigation strategies
For Amazon RDS Proxy:
- Use initialization queries: move common
SETstatements into the proxy’s initialization query configuration. RDS Proxy applies these settings to each new backend connection, so individual clientSETcalls become unnecessary. - Use
ALTER ROLE ... SET: configure default parameters at the role level rather than issuing session-levelSETcommands. These defaults are applied on connection without triggering pinning. - Use transaction-scoped state: prefer
SET LOCALcommands within a transaction block. TheSETcommand (without theLOCALkeyword) causes pinning in RDS Proxy. - Use transaction-level advisory locks: replace
pg_advisory_lock()withpg_advisory_xact_lock()where your locking semantics allow it. - If you’re using a client-side pooling library that issues
DISCARD ALLas a reset query, note that it will not remove pinning that RDS Proxy has done for your session. - Isolate pinning-prone workloads: route
LISTEN/NOTIFYsubscribers or SQL-level prepared-statement-heavy workloads through a dedicated direct connection, keeping the proxy pool clean for transactional traffic.
Note: Unlike RDS for MySQL, RDS Proxy for PostgreSQL does not currently offer session pinning filters to selectively exempt operations from pinning.
For PgBouncer transaction mode:
PgBouncer doesn’t use pinning. It assumes that you manage your session state to avoid session leakage. As a workaround, it provides the server_reset_query_always non-default parameter that resets session state between transactions. Applications must be designed to be stateless between transactions. PgBouncer 1.21+ handles protocol-level prepared statements transparently by re-preparing them on each new backend connection. Session-level features (LISTEN/NOTIFY, advisory locks, temp tables) require a dedicated session-mode connection or a direct bypass connection. The trade-off: PgBouncer forces you to design for statelessness upfront, while RDS Proxy preserves correctness by design, falling back to pinning when a connection cannot be safely reused.
Performance, cost, and operational trade-offs
The following sections compare latency, cost, and the operational effort each approach requires.
Latency
PgBouncer’s event-driven, single-process architecture delivers low-latency responses per query because PgBouncer uses libevent to avoid needing to read full network packets in one go. This is especially true in sidecar deployments where the pooler runs on the same host as the application or database, avoiding the extra network hop. RDS Proxy, by contrast, routes connections through a separately managed Multi-AZ infrastructure layer, which introduces an additional network hop. For ultra-low-latency online transaction processing (OLTP) workloads that are sensitive to per-query overhead, the extra hop can become a factor. The magnitude of the difference depends on your deployment topology, instance sizes, and network configuration. A sidecar PgBouncer typically has lower network latency than a remote proxy, but it cannot provide the built-in Multi-AZ failover that RDS Proxy offers.
Cost considerations
The cost of each option breaks down into the compute it runs on, the operational effort to keep it running, and how it scales. The following table compares the two approaches across these dimensions.
| Component | PgBouncer | RDS Proxy |
| Compute | Amazon EC2/EKS instances to run PgBouncer (size depends on connection count), typically two or more for high availability | Charged per vCPU or ACU hour of the associated database instance. See Proxy pricing for region-specific pricing. Minimum charge of 8 ACU hours on Aurora serverless and 2 vCPU hours on provisioned instances. |
| Operational cost | Deployment automation, monitoring, patching, HA configuration, on-call | Included in service (reduced operational overhead) |
| Scaling | Manual (add instances, configure load balancing) | Automatic |
Monitoring and observability
PgBouncer exposes statistics through its SHOW admin commands (SHOW STATS, SHOW POOLS, SHOW CLIENTS, SHOW SERVERS). Community Prometheus exporters (such as pgbouncer_exporter) make these metrics available for Grafana dashboards and alerting. You have full control over what you monitor but must build and maintain the observability stack. RDS Proxy publishes metrics to Amazon CloudWatch (DatabaseConnections, ClientConnections, DatabaseConnectionsCurrentlySessionPinned, QueryRequests, AvailabilityPercentage), providing alarms, dashboards, and anomaly detection without additional infrastructure, though you are limited to the metrics AWS exposes.
How to discover pinning in RDS Proxy
- In the AWS console, on the RDS dashboard, open the Proxies section and select your proxy.
- Select the monitoring tab, then change the menu from Proxy health overview to Additional CloudWatch metrics.
- Search for “pinned” to find the
DatabaseConnectionsCurrentlySessionPinnedmetric, which shows the number of pinned sessions. - On the configuration tab for the proxy, select the link to the proxy’s CloudWatch Logs.
- Under Log Streams, enter the search string “pinned” and select Search all log streams.
- You can then select a time window and see which operations caused the pinning, such as
SETstatements and so on.
Conclusion
In this post we have shown how PgBouncer and Amazon RDS Proxy each solve PostgreSQL connection pooling with different trade-offs. Amazon RDS Proxy is the recommended starting point as the fully managed option. Consider self-managed PgBouncer when a specific constraint makes it a better fit, such as colocated sidecar latency or statement-level pooling.
To get started, audit your connection patterns to determine your peak-to-idle ratio. Use this to identify whether your workload has significant connection bursts, or whether you are nearing your maximum connection limit. These indicate that a connection pooler might be beneficial. Then use this guide to decide which connection pooler is right for you, based on your operational reality and the configuration changes that you are able to make. To learn more, see the Amazon RDS Proxy documentation and Amazon RDS Proxy pricing.