PRODSovereign European BaaS platformOpen Dashboard →

Performance · 11 min read

PgBouncer vs Supavisor vs PgCat: which Postgres pooler?

Affane Daylami · Fondateur · June 9, 2026

Back to blog

PgBouncer, Supavisor and PgCat all share Postgres connections, but they do not address the same problem. PgBouncer remains the historical standard: lightweight, in C, natively integrated into the Kubernetes ecosystem via CloudNativePG. Supavisor was built by Supabase for a specific need, to hold thousands of databases behind a single service instead of one process per database. PgCat, written in Rust, adds to the classic pooling of sharding and load balancing between replicas. At Aurabase, data-plane traffic goes through PgBouncer in transaction mode. This is directly shown in the repository code: Helm chart, CNPG Pooler resource and local k3d configuration all converge towards the same choice.

This English text was generated automatically from the French original and has not been reviewed yet.

This article compares the architecture of the three poolers: language, pooling modes, single or multi-tenant model, functions beyond pure pooling. Tembo and PkgPulse have published numerical comparisons of these three tools, but we have not reproduced any of their measurements ourselves. Our editorial position on benchmarks, detailed in our benchmark methodology, is to never republish a figure that we have not verified ourselves. What you'll find here instead: the actual architecture of each tool, and how Aurabase actually routes its Postgres traffic, verified section by section in the source code.

The essentials
  • PgBouncer (C) remains the most proven pooler and best integrated with Kubernetes: CloudNativePG relies directly on it for its Poolerresource.
  • Supavisor (Elixir, Supabase project) targets a different problem: serving thousands of databases from the same service, rather than one pooler per database.
  • PgCat (Rust) adds application sharding, load balancing between replicas and automatic failover to raw pooling.
  • The Aurabase repository shows PgBouncer used at two levels: a shared deployment for the shared fleet, and a Pooler resource managed by CloudNativePG per dedicated tenant. Both operate in transaction mode.
  • PostgREST and theaura-db administration pool voluntarily remain in direct connection to Postgres, without going through the pooler: transaction pooling would break their schema reloading and their session locks.
#
Panorama

Three poolers, three philosophies

PgBouncer minimizes, Supavisor pools on a multi-tenant scale, PgCat adds network functions to raw pooling. None of the three is a direct replacement for the other two, although they are often compared term for term on the same pages.

LanguageCElixir (BEAM)Rust
Pooling modesSession, transaction, statementSession, transactionSession, transaction, statement
Tenancy modelOne target cluster per instance, designed to be single-tenantNative multi-tenant: a service for many databasesA target cluster, sharding by partition key
Beyond poolingNo additional functions, deliberately minimalAdmin HTTP API, dynamic tenant registrationSharding, load balancing and failover between replicas
Native Kubernetes integrationYes: CloudNativePG Resource PoolerNot natively documented to dateNot natively documented to date
OriginThe historical standard of Postgres poolingBuilt by Supabase for its own multi-tenant cloudBorn at Instacart, maintained today by PostgresML

Columns, in order: PgBouncer, Supavisor, PgCat. Architecture characteristics according to the official filings of each project, to be confirmed on the version you are deploying, the ecosystem evolving quickly on this point.

#
PgBouncer

The historic standard, lightweight and integrated into Kubernetes

PgBouncer only does one thing: pool Postgres connections, without any additional functions. This deliberately narrow scope largely explains its longevity and its adoption as a basic building block in most Postgres stacks in production.

Three pooling modes are available. Session mode opens one server connection per client connection, the most permissive. Transaction mode reuses a server connection between several clients, released at each end of a transaction. Statement mode goes even further, and is rarely used in production. It is the transaction mode that brings the real pooling gain, but it imposes strict rules. Any session state (SETvariables, advisory locks, LISTEN/NOTIFY) does not survive beyond a transaction. We detail these rules and their pitfalls in our dedicated article on the transaction pooling mode of PgBouncer.

Historically single-process, a PgBouncer instance uses a single CPU core by default. Running multiple instances behind the same port (via SO_REUSEPORT) is a more recent evolution of the project, not an initial design feature. On the authentication side, PgBouncer supports a configurable auth_query, an SQL function executed at each connection to dynamically resolve a role's password. This mechanism avoids depending on a static file listing each user in advance. It is exactly this mechanism that Aurabase uses for its per-project roles (section 05).

Astuce

PgBouncer is the pooler that CloudNativePG natively deploys behind its Poolerresource. On a Postgres cluster managed by the CloudNativePG operator, activating a managed pooler amounts, in practice, to activating PgBouncer without configuring it by hand.

#
Supavisor

Supabase's cloud-native multi-tenant pooler

Supavisor addresses a problem that PgBouncer was never designed to solve at this scale. This involves serving a very large number of distinct tenant databases from a single service, rather than one pooler instance per database. Written in Elixir and executed on the Erlang virtual machine (BEAM), the project is developed and maintained by Supabase, in open source, on its own GitHub repository.

The native multi-tenant model is the real structural difference. Where a classic PgBouncer fleet requires one process (or a set of dedicated connections) per target base, Supavisor works differently. It dynamically registers tenants via an HTTP administration interface, and routes each incoming connection to the correct database without restarting the service. Supabase migrated its own Cloud projects from PgBouncer to Supavisor for this very reason. A classic pooling cluster, one per database, does not scale to a multi-tenant cloud that hosts hundreds of thousands of projects.

This architectural choice has a documented downside. Feature parity with PgBouncer on advanced cases took time to stabilize after project launch. Two examples: certain behaviors of LISTEN/NOTIFY, and the fine management of prepared statements in transaction mode. Check your version before migration if your application depends on these specific behaviors.

#
PgCat

The Rust outsider: native sharding and load-balancing

PgCat is explicitly positioned as an alternative to PgBouncer, written in Rust. It adds network functions to classic pooling that neither PgBouncer nor Supavisor natively embeds. Three in particular: application sharding by partition key, load balancing between read replicas, and automatic failover away from a failed replica. The project was born at Instacart before being taken over and maintained today by PostgresML.

Concretely, PgCat can play the role that two distinct layers would normally occupy: a connection pooler and an application proxy for routing between several Postgres instances. A team that was already sharding its data by hand can simplify its code with PgCat. Same thing for a logic for distributing reads between replicas developed internally: a dedicated network layer directly replaces it.

The opposite compromise also exists: PgCat is a younger project, with a much smaller ecosystem of documentation and production feedback than PgBouncer. Adopting its sharding and failover functions also means agreeing to depend on the maturity of this specific component, not just on its pooling capacity.

#
Checked in code

What the Aurabase code shows: PgBouncer everywhere, except where transaction pooling breaks everything

The Aurabase repository deploys PgBouncer at two separate tiers, both in transaction mode. For the shared fleet, the Helm chart defines a dedicated PgBouncer deployment in front of the shared data-plane (deploy/helm/aurabase/templates/infra/pgbouncer.yaml, image edoburu/pgbouncer). For a tenant on a dedicated instance, the provisioner generates a resource Pooler natively managed by CloudNativePG (deploy/cnpg/tenant-pooler.yaml, rendered by k8s_tenant.rs). Neither uses Supavisor or PgCat. The code does not document an explicit comparison that preceded this choice. On the other hand, it shows a deep and already operational integration with the CloudNativePG ecosystem, consistent with the fact that PgBouncer is the native pooling brick.

transaction
POOLING MODE
Shared fleet and pooler by dedicated tenant
1000
MAX CUSTOMER CONN
Simultaneous customer ceiling, Helm chart default
80
DEFAULT POOL SIZE
Server connections by (base, role), default Helm chart

However, not everything goes through the pooler, and this is a deliberate choice documented in the code itself. PostgREST remains connected live to Postgres, never via PgBouncer. The Helm chart comment is explicit about the reason: transaction pooling would break its schema reload, which depends on a LISTEN on the pgrstchannel. This mechanism is incompatible with recycled server connections between clients. Theaura-db administration pool (schema, DDL, session advisory locks) also remains in direct connection, for the same basic reason. Non-transaction-scoped SET search_path and session locks do not survive a transaction mode pooler.

deploy/helm/aurabase/templates/infra/pgbouncer.yaml (extrait commenté)yaml
# Data-plane aura-db: via PgBouncer, everything is transaction-scoped (SET LOCAL)
DATABASE_URL: postgresql://aura_authenticator:...@pgbouncer:5432/aura_db_master

# Pool admin (DDL, introspection): DIRECT on Postgres, never PgBouncer
# SET search_path non-LOCAL + session locks break transaction pooling
ADMIN_DATABASE_URL: postgresql://aura:...@postgres:5432/aura_db_master

Authentication follows the auth_query pattern described in section 02: AUTH_QUERY: SELECT * FROM pgbouncer.user_lookup($1), without a static userlist.txt file. This is what allows roles dynamically created per project (project_<uuid>_authenticator) to authenticate via PgBouncer without redeploying the pooler for each new project.

An operational lesson found in writing this article

The PgBouncer healthcheck comment in the local Kubernetes manifests documents a real bug, already fixed. An pg_isready run against PgBouncer only validates the proxy handshake, never the actual connection to the Postgres backend it relays. PgBouncer responds “accepting connections” even when the backend is stopped, queuing the requests. Result observed during a destructive test: the service remained healthy for 5 consecutive cycles while Postgres was unreachable. The fix replaces the check with a true end-to-end psql request through the pooler, all the way to the backend. Result after correction, in the same test replayed: unhealthy detected in 7 cycles, approximately 35 seconds.

One last detail, minor but revealing: the Helm chart pins edoburu/pgbouncer:v1.24.1-p1 by default, while the local k3d bench uses v1.25.2-p0. It's not an architectural choice, just a slight lack of version synchronization between two environments, the kind of detail that a code review catches faster than a blog post. We document it as it is rather than dressing it up. For details of the schema partitioning that this pooler serves, see our article onmulti-tenant RLS isolation.

#
Decision

How to choose between the three

Choose PgBouncer if…

  • Postgres cluster managed by CloudNativePG or Kubernetes in general
  • You want the most proven and well-documented pooler
  • A target base per pooler instance suits you

Choose Supavisor if…

  • Hundreds or thousands of bases behind the same service
  • Need to register tenants dynamically via an API, without redeployment
  • Already in the Supabase ecosystem or willing to depend on it

Choose PgCat if…

  • Application sharing already in place or planned at the pooler level
  • Load balancing and failover replica without separate application layer
  • Comfortable with a younger project, less documented than PgBouncer

Whatever pooler is chosen, it does not replace the sizing of Postgres itself. Pool size and server max_connections should be thought of together, not one after the other. A generous pool in front of a too low max_connections simply shifts the saturation from one level to another. Our guide on tuning max_connections details the sizing formula to apply before setting the size of your pool.

#
Frequently asked questions

What we get asked most often

PgBouncer and Pgpool-II, what’s the difference?+
Pgpool-II goes beyond connection pooling: load distribution between replicas, in-memory query cache, application replication. PgBouncer only does one thing: pool connections. This is partly what explains why it is often chosen as a basic building block, supplemented by other tools if necessary, rather than replaced by a broader platform.
Can we use PgBouncer with Supabase?+
Historically yes: Supabase relied on PgBouncer before developing Supavisor. Both remain presented in their official documentation according to the connection context: IPv4 direct, pooler transaction, pooler session. This precise point evolves quickly, to check when configuring a project.
Does PgCat manage prepared statements in transaction mode?+
Since version 1.21, PgBouncer tracks and re-prepares protocol prepared statements on the fly in transaction mode, a behavior documented in the Aurabase Helm chart itself. PgCat claims similar server-side support. We have not measured either one nor the other under real load conditions, so check on your own traffic before making it a decisive selection criterion.
Is Supavisor open source?+
Yes, the repository is public on GitHub (supabase/supavisor). It is a project distinct from Supabase's Postgres core, written in Elixir, designed from the start for multi-tenant rather than adapted after the fact.
#
In summary

There is no universal pooler, only a good fit for your tenancy

PgBouncer, Supavisor and PgCat solve three variations of the same problem, not three versions of the same tool. PgBouncer remains the safest choice when your platform already relies on Kubernetes and CloudNativePG, or when you simply want the most documented pooler. Supavisor becomes relevant beyond a certain number of bases serving from the same service. PgCat is worth the detour if you miss sharding and replica failover at the network level, provided you accept the maturity of a younger project.

The Aurabase code shows a consistent, not neutral, choice: PgBouncer in transaction mode, at two levels, shared fleet and CNPG pooler per dedicated tenant. Two documented exceptions remain, for PostgREST and for schema administration. If you want to see this project siloing in action rather than on paper, our Performance page documents the associated measurement methodology.

READY TO DEPLOY?

Your backend in five minutes.

No credit card required · 500 MB free · 50,000 MAU