PostgreSQL connection pooling sounds simple:
- Lots of client connections β fewer database backend processes.
But the hard part is not creating the pool.
The hard part is deciding what happens to session state when one logical client session uses different PostgreSQL backend processes over time.
PgBouncer has long been one of the most popular lightweight PostgreSQL connection poolers. YugabyteDB now has another option: YSQL Connection Manager, a connection pooler built directly into YugabyteDB.
Both can dramatically reduce the number of PostgreSQL backend processes required by an application.
But they approach the session-state problem very differently.
YSQL Connection Manager (YCM) became generally available in YugabyteDB 2026.1.1 and is built into every YugabyteDB node.
Why Do We Need a Connection Pooler?
PostgreSQL traditionally creates a dedicated backend process for each connected client. YugabyteDB inherits that process-per-connection architecture through YSQL.
That is usually fine with a modest number of connections.
Things get more interesting when an application consists of many microservices, Kubernetes pods, application servers, or other independently scaling components. Each component may maintain its own connection pool, and suddenly the database can be responsible for far more backend processes than the workload’s actual query concurrency requires.
A connection pooler inserts another layer between those logical application sessions and the database backends:
The important word here is multiplexes.
The client connection and PostgreSQL backend no longer have to be the same thing.
PgBouncer in 60 Seconds
PgBouncer is a lightweight, open-source PostgreSQL connection pooler that applications connect to as though it were PostgreSQL. It maintains and reuses connections to the actual data.
| Mode | Backend Is Released… | Trade-Off |
|---|---|---|
| Session | When the client disconnects | Maximum PostgreSQL compatibility, but less backend multiplexing |
| Transaction | When the transaction ends | Much better connection reuse, but some session-level features are incompatible |
| Statement | After each statement | Most aggressive; multi-statement transactions aren’t allowed |
PgBouncer’s own documentation describes session pooling as supporting all PostgreSQL features, while transaction pooling deliberately breaks some session-level assumptions because a client may receive a different backend for its next transaction.
And that brings us to the interesting part.
The Session-State Problem
Imagine an application establishes a connection and does this:
SET app.tenant_id = 'customer_42';
Later, it runs:
SELECT *
FROM orders;
Perhaps Row-Level Security or application logic depends on app.tenant_id.
With a traditional PostgreSQL session, both operations execute within the same backend process, so the session setting remains available.
But consider transaction pooling:
That question is at the heart of the difference between PgBouncer transaction pooling and YSQL Connection Manager.
How YSQL Connection Manager Changes the Model
YSQL Connection Manager also performs transaction-level pooling, allowing a backend to return to the pool after a transaction.
But instead of expecting the application to avoid session state, YugabyteDB tracks the session state inside the database query layer.
When that client later receives a different backend, YSQL Connection Manager restores the appropriate state before the next transaction executes. It does not accomplish this by simply parsing SQL statements; the YugabyteDB query layer reports the state changes directly.
This lets YugabyteDB separate the logical application session from the physical PostgreSQL backend much more aggressively while retaining PostgreSQL-compatible behavior.
PgBouncer vs. YSQL Connection Manager
Here is the comparison I find most useful:
| Capability | PgBouncer | YSQL Connection Manager |
|---|---|---|
| Deployment | Separate pooler tier/process | Built into every YugabyteDB node |
| Pooling model | Session, transaction, or statement | Transaction-level reuse with automatic session-state handling |
| General SET / RESET state |
Session mode: yes Transaction mode: cannot generally be relied upon |
Preserved without pinning |
| Protocol-level prepared statements | Supported in modern versions with prepared-statement tracking | Preserved without pinning |
| SQL-level PREPARE | Not compatible with transaction pooling | Automatically pins the session |
| Temporary tables | Limited in transaction mode; session-dependent forms are incompatible | Automatically pins when required |
| LISTEN | Not compatible with transaction pooling | Automatically pins the session |
| WITH HOLD cursors | Not compatible with transaction pooling | Automatically pins the session |
| Scaling the pooler | Single-threaded per instance; deploy additional instances to use more cores | Multi-threaded and present on each YugabyteDB node |
| Application endpoint | Application connects to PgBouncer | Uses the standard YSQL endpoint when enabled |
| Operational ownership | Deploy, configure, monitor, patch, and scale separately | Managed as part of YugabyteDB |
PgBouncer’s transaction-mode restrictions and prepared-statement behavior come directly from its current feature and configuration documentation. PgBouncer is also single-threaded per instance; multiple instances can use SO_REUSEPORT to spread connections across CPU cores.
YSQL Connection Manager’s session-state preservation, automatic pinning, built-in architecture, and multi-threaded operation are documented by YugabyteDB.
The Really Clever Part: Pin Only When Necessary
Not all session state can safely move between PostgreSQL backends.
YSQL Connection Manager therefore uses two strategies.
Protocol-level prepared statements
NOTIFY
Transaction-scoped advisory locks
Temporary tables
WITH HOLD cursors
SQL-level PREPARE
SET ROLE
The application does not have to choose between a global session mode and transaction mode. YSQL Connection Manager makes the decision at the individual-session level: continue multiplexing when state can safely move, or pin when PostgreSQL semantics require a persistent backend.
A Small Example: SET
Suppose an application uses a custom configuration parameter:
SET app.tenant_id = 'customer_42';
The application might expect that value to remain associated with its logical database session for subsequent transactions.
With PgBouncer session pooling, that’s straightforward because the client keeps the same backend.
With PgBouncer transaction pooling, applications should not assume that general SET state survives across transactions because the next transaction may execute on another backend. PgBouncer explicitly lists SET/RESET as incompatible with transaction pooling.
YSQL Connection Manager takes another approach:
SET app.tenant_id = 'customer_42'
That means the application can retain PostgreSQL session semantics while still benefiting from transaction-level backend reuse.
Prepared Statements Deserve a Special Mention
There is an outdated assumption that PgBouncer transaction pooling simply does not support prepared statements.
That is no longer generally true.
Modern PgBouncer can track protocol-level named prepared statements in transaction pooling. PgBouncer rewrites and manages those prepared statements so they are available on whichever backend is assigned to the client. SQL-level PREPARE, EXECUTE, and DEALLOCATE, however, are not tracked the same way and remain incompatible with transaction pooling assumptions.
YSQL Connection Manager also supports protocol-level prepared statements without pinning. If an application explicitly uses SQL-level PREPARE, YSQL Connection Manager preserves correctness by pinning that session to a backend.
That’s an important distinction when comparing the two technologies.
Architecture Matters Too
PgBouncer is deliberately lightweight and generic. You deploy it somewhere between the application and the database.
YSQL Connection Manager is part of YugabyteDB itself.
When enabled, each YB-TServer starts a YSQL Connection Manager process alongside PostgreSQL. By default, Connection Manager listens on the normal YSQL port, 5433, while the underlying PostgreSQL backend is moved to another available port.
PgBouncer is also single-threaded per process. Its documentation describes using multiple instances with SO_REUSEPORT when more CPU cores are required. YSQL Connection Manager is multi-threaded, and because it exists on every YugabyteDB node, pool capacity grows naturally as the cluster grows.
Does This Mean PgBouncer Is Bad?
Absolutely not.
Β PgBouncer is small, mature, flexible, and extremely useful.
If an application is already designed around stateless transactions, PgBouncer transaction pooling can be an excellent fit. If complete session compatibility is more important than multiplexing, PgBouncer session pooling supports the full PostgreSQL feature set.
The interesting question for a YugabyteDB deployment is whether you still want to operate a separate pooling tier when the database now has a connection manager that:
One Important Caveat: Pooling Does Not Create CPU
Supporting thousands of logical client sessions does not mean thousands of queries should execute simultaneously.
Β Connection pooling reduces the number of physical PostgreSQL backends required to support those client sessions. The actual concurrency your cluster can efficiently execute still depends on workload characteristics, CPU, transaction duration, query complexity, and how frequently sessions become pinned.
Final Takeaway
PgBouncer and YSQL Connection Manager solve the same fundamental problem:
- Too many application connections should not require the same number of PostgreSQL backend processes.
The biggest difference is what happens after those backend processes start being shared.
For applications running on YugabyteDB, that native integration can remove an entire external pooling layer while allowing applications to retain far more of the PostgreSQL session behavior they already expect.
Have Fun!
Meet Podzilla… the coolest part of our move so far! ππ¬ Watching this machine deliver our moving pod to the driveway was even more fun in person. Now, if only it could pack all the boxes, too! π¦π
