← All articles

PgBouncer vs PgCat vs Pgpool-II: choosing a PostgreSQL connection pooler

11 min read
PostgreSQLpgBouncerHigh AvailabilityPerformance

Three tools keep showing up when a PostgreSQL database starts buckling under connection count: PgBouncer, PgCat and Pgpool-II. They are not interchangeable. They solve an overlapping problem with very different architectures, and the right choice depends far more on what else you need — read scaling, failover, sharding — than on raw throughput. Here is how we actually pick between them.

The problem underneath all three is the same. PostgreSQL forks a backend process per connection. A few hundred idle application connections cost real memory and scheduler time, and a connection storm — every app server reconnecting after a blip — can tip a healthy primary over. A pooler sits between the application and Postgres, keeps a small set of server connections warm, and multiplexes many client connections onto them.

What each one actually is

PgBouncer — the minimalist

PgBouncer is a small, single-purpose pooler written in C. For most of its life it ran as a single process on one core; recent versions can run several worker processes with SO_REUSEPORT, but it stays deliberately lightweight. It offers three pooling modes — session, transaction and statement — and nothing else. No load balancing, no query routing, no failover. It does one job and gets out of the way. The trade-off that bites people is the pooling mode: transaction mode is where the connection savings come from, but it quietly breaks session state, and prepared statements needed special handling until recently. We covered that failure surface in detail in what breaks when you switch to transaction pooling.

PgCat — the modern multi-tool

PgCat is a newer pooler written in Rust, multi-threaded from the start. It keeps PgBouncer-style transaction pooling but adds the things PgBouncer deliberately leaves out: load balancing across replicas, automatic read/write splitting, sharding, health checks and failover. It aims to be a drop-in that also scales reads without touching the application. It is used in production, but it is far younger than the other two, with a smaller operational track record and fewer people who have debugged it at 3 a.m.

Pgpool-II — the veteran all-in-one

Pgpool-II is the oldest of the three and by far the most feature-dense. On top of connection pooling it does load balancing, automatic failover, online recovery of a rebuilt replica, an in-memory query cache, and a watchdog that gives Pgpool itself a floating virtual IP for high availability. It can run an entire replication-and-failover stack on its own. That power is also its reputation: the configuration surface is large, query-routing behaviour surprises people, and a misunderstood load-balance rule can send a write to a replica.

The comparison that matters

PgBouncerPgCatPgpool-II
Language / threadingC, light (multi-proc optional)Rust, multi-threadedC, multi-process
Pooling modessession, transaction, statementtransactionsession-style reuse
Read/write split & LBNo (use HAProxy/DNS)Yes, built inYes, built in
Failover / HAExternal (Patroni + HAProxy)Health checks; external promoteBuilt-in + watchdog VIP
ShardingNoYesNo
Query cacheNoNoYes (in-memory)
Config complexityLowMediumHigh
Maturity / track recordVery highLow–medium (young)Very high
FootprintTinySmallLarger

One caveat on the table: PgBouncer added prepared-statement support in transaction mode (max_prepared_statements) in version 1.21, which closed its most painful historical gap. If you rejected it a few years ago for that reason, re-test it.

Pooling mode: where the savings — and the bugs — live

All three can reuse server connections, but the deep multiplexing — and the real reduction in backend count — comes from transaction pooling: a server connection is handed back at every COMMIT, so a thousand mostly-idle clients share a few dozen backends. The cost is that anything which assumes a stable session — SET, session-level advisory locks, LISTEN/NOTIFY, temporary tables, unnamed prepared statements — can land on a different backend than expected. This is a property of transaction pooling, not of any one tool. Decide your pooling mode first; it constrains the application more than the choice of pooler does.

Read scaling and load balancing

This is the first real fork in the road. PgBouncer does not route queries — it cannot tell a SELECT from an UPDATE, and it will happily send everything to whatever server it is pointed at. To scale reads with PgBouncer you put something else in front (HAProxy, a DNS or VIP, or application-side routing) and run a pool per target. PgCat and Pgpool-II both do read/write splitting natively: writes to the primary, balanced reads to replicas, with the pooler tracking replica health. If transparent read scaling is a hard requirement and you do not want to change the application, that requirement alone pushes you toward PgCat or Pgpool.

High availability and failover

When the primary dies, something has to notice, promote a replica, and repoint writes. PgBouncer owns none of that — it expects an external stack, most commonly Patroni for orchestration plus HAProxy or a VIP to move the write endpoint. That is a well-trodden, boring-in-a-good-way design. PgCat runs its own health checks and can steer reads away from a sick replica, but you still pair it with a promotion tool for the primary. Pgpool-II can run the whole show — detect failure, trigger a failover script, recover a replica — and its watchdog keeps Pgpool itself available behind a virtual IP. The more Pgpool does, the more there is to get right, and the more carefully its failover scripts need testing against real outages, not just a clean shutdown.

What it costs to operate

The honest differentiator is not benchmark numbers — all three move plenty of queries — it is operational cost. PgBouncer is the one you can hand to a team and trust: tiny config, predictable behaviour, a decade of other people's bug reports already written. Pgpool-II gives you the most features and the most ways to shoot yourself: load-balance rules that route a write to a replica, watchdog split-brain, query-cache invalidation surprises. PgCat sits in the middle on features but carries the risk of youth — fewer battle stories, faster-moving code, a smaller pool of engineers who have operated it under failure.

How we choose

Our default is boring on purpose. If the goal is simply to tame connection count and protect the primary, we reach for PgBouncer and, where HA is needed, pair it with Patroni and HAProxy. It is the lowest-risk path and the easiest to reason about during an incident.

When the application needs transparent read scaling across replicas and the team is comfortable running a younger Rust service, PgCat earns its place — it delivers read/write splitting without a separate routing layer. When an organisation wants a single component to own pooling, load balancing and failover together, or has already standardised on it, Pgpool-II is the answer, provided the complexity is respected and the load-balance rules are tested against real traffic rather than assumed.

None of this is a throughput verdict. For most SMB and mid-market workloads all three are fast enough; the deciding factors are read scaling, failover ownership and how much operational surface the team can carry. Benchmark only after the shortlist is down to one or two.

Bottom line

  • •Just need pooling and a smaller connection footprint → PgBouncer. Add Patroni + HAProxy for HA.
  • •Need transparent read/write split and replica load balancing, a modern stack, and your team is fine with a younger tool → PgCat.
  • •Want one component for pooling + load balancing + failover, and can carry the complexity → Pgpool-II.
  • •Pick your pooling mode (transaction vs session) before you pick the tool — it shapes the application more than the pooler does.

Moving off SQL Server at the same time? The pooler question rides along with a dozen others — see what actually breaks in a SQL Server to PostgreSQL migration.

PostgreSQL connections out of control?

Pooler selection, read/write split and HA design — €65/hour, fixed-scope, with the restore and failover testing that proves the setup before it reaches production.