Loch Ness Monster emerging from a swimming pool surrounded by ducks, with pool ladder and facilities visible in the background.

Everyone into the Pool covered five PostgreSQL connection poolers and waved at two more on the way out. That left a fair number of people standing at the edge with a towel: anyone running on Azure or Google Cloud, anyone who wants PgBouncer’s job done on more than one core, and anyone whose application already has a pool and would like to know whether that counts.

This is the rest. First, five more poolers you can download and run, in order of appearance, with the same five questions as before: where it came from, how it runs, how it pools, where it will hurt you, and what else it does. Then the poolers that come attached to a managed database, the ones that live at the edge of someone else’s network, and the one inside your application.

The vocabulary is unchanged. Session pooling holds a server connection for as long as the client is connected. Transaction pooling hands it back at COMMIT, and the entire difficulty of transaction pooling is what to do about session state: SET, prepared statements, advisory locks, LISTEN, temporary tables.

Odyssey: PgBouncer, plural

Odyssey is Yandex’s pooler, and the one built into Yandex’s managed PostgreSQL service. The repository starts in November 2016, 1.0 was tagged in December 2019, and the current release is v1.5.2 (September 13, 2026). The license is BSD. The README now leads with “AI-ready,” which I will not hold against it.

It is written in C and it is multi-threaded, on a coroutine library of its own called machinarium. Worker threads handle authentication and proxying, and all of them share the same server connection pools, so there is one pool to size rather than one per process. workers defaults to 1; you raise it when TLS is eating a core. It runs on Linux, on x86, and nowhere else.

Pools are defined by rules, one per database and user pair, each with its own mode, limits and authentication. The documentation describes session and transaction pooling (and assures you that “Odyssey is really good at pooling”); the configuration parser also accepts pool "statement". In transaction mode, Odyssey remembers the parameters a client has set and re-applies them when the client is attached to a different server connection; that is maintain_params, and it is on by default. When a client leaves a server connection in a bad state, pool_rollback rolls back the open transaction and pool_cancel cancels the running query, so the connection goes back to the pool instead of being closed. Prepared statements work in transaction mode with pool_reserve_prepared_statement yes. Pinning a connection after LISTEN exists, and is marked experimental.

The quirks are mostly those of a tool built for one very large installation. The configuration file is its own format, with its own grammar. The documentation is thinner than PgBouncer’s, and in places behind the code, as the statement mode shows. Features arrive when Yandex needs them.

What Yandex has needed is considerable. A storage can list several hosts, and target_session_attrs (since 1.4.1) sends a client to a read-write host, a read-only one, or a standby by preference, with availability-zone-aware host selection. There is an online restart on SIGUSR2 (since 1.5.0), and a “soft OOM” mode (1.5.1) that stops accepting new connections while a named process is over a memory limit. Authentication covers SCRAM, MD5, client certificates, PAM, LDAP, auth_query and an HBA file.

pgagroal: a process for everyone

pgagroal was started by Jesper Pedersen in August 2019 and named after a river beach in Portugal. 1.0.0 followed in November 2020, 2.0.0 in January 2026, and the current release is 2.1.0 (April 28, 2026). The license is BSD.

Its process model is the one the first article spent some time being rude about: fork(), one process per client connection. The project’s reasoning is stated plainly, that a crash on one connection should not take the whole pool down. What makes this different from Pgpool-II is that the pool itself lives in a shared memory segment, with each connection’s state tracked by atomic operations, so a backend connection is not trapped in the child that opened it. Network I/O goes through io_uring on Linux. max_connections defaults to 100.

pgagroal calls its modes pipelines, and there are three. The performance pipeline is session pooling with everything optional removed, including TLS. The session pipeline is session pooling with everything. Both run DISCARD ALL when a client disconnects. With pipeline = auto, you get performance until you turn on TLS or failover, and then you get session.

The transaction pipeline is where care is needed. SET, LISTEN, WITH HOLD cursors and SQL-level PREPARE are unsupported, as usual. Less usual: nothing is reset between clients. The documentation says so directly: “Session state such as SET, temporary tables and cursors is not reset between clients sharing the same backend connection.” It also assumes every client of a given user and database sends the same startup parameters, and idle_timeout and max_connection_age mostly stop applying. Treat pgagroal as a session pooler. That is what it was built to be.

Around the pool there is prefill, per-user and per-database connection limits, a failover hook that runs a script of your choosing, an authentication query, a Prometheus endpoint with a Grafana dashboard, and a management CLI.

Supavisor: a pooler for landlords

Supavisor is Supabase’s pooler. The first commit is from January 2023, 1.0.0 was tagged that December, and the current release is 2.9.13 (September 10, 2026). The license is Apache 2.0.

Supabase’s stated reasons for writing it are specific to hosting other people’s databases. PgBouncer on the database host uses resources the customer is paying for; serverless clients arrive in enormous numbers; and resizing a database without downtime needs something outside the database to hold the connections while it happens.

So Supavisor is a cluster. It is written in Elixir, runs on the Erlang VM, and keeps its tenant configuration in a PostgreSQL database of its own. A client names its tenant in the user name (postgres.dev_tenant), by TLS SNI, or in options. The first node to receive a connection for a tenant starts that tenant’s pool, and every other node proxies to it. One pool per tenant in the whole cluster means the number of backend connections is exactly default_pool_size, with no arithmetic across nodes. The README claims a million connections on a cluster and 250,000 idle connections on one 16-core node.

It has transaction and session modes, plus a “native” mode that proxies straight through for migrations. The documentation is brief on session state: “Both transaction and session pool mode behavior for Supavisor is the same as PgBouncer.” Named prepared statements in transaction mode sit behind a feature flag, and Supabase’s own hosted documentation still says transaction mode does not support them.

The limitations follow from the design. The README puts throughput “within 90%” of PgBouncer’s, so you are trading some speed for the clustering. You need a metadata database and several nodes to replace one small C program. Load balancing across replicas and query caching are listed as future work. If you host databases for thousands of customers, this is built for you. If you have one database, it is a great deal of machinery.

PgDoorman: the other descendant

PgDoorman comes from Ozon. Its documentation says it “was originally forked from PgCat but has since been rewritten,” and that it “is now a separate codebase.” The public repository begins on March 5, 2025, already at version 1.7.7, and the project claims three years in production. The current release is v3.11.0 (June 26, 2026). The license is MIT.

That makes two descendants of PgCat. PgDog went toward sharding and the AGPL. PgDoorman stayed a pooler and stayed MIT.

It is Rust, multi-threaded (worker_threads defaults to 4), with one pool shared by all threads. Configuration is YAML or TOML.

There are two modes, transaction and session, and no statement mode, on purpose. Session state is handled the way PgCat handled it: PgDoorman notices SET, PREPARE and DECLARE CURSOR and sends RESET ALL, DEALLOCATE ALL or CLOSE ALL when the connection is checked in. Nothing leaks, and nothing persists; the documentation tells clients that depend on SET to use session mode. LISTEN only works inside a transaction.

Where it has put its effort is prepared statements. PgBouncer handles named ones. Several popular drivers (pgx, Npgsql and asyncpg, by PgDoorman’s account) use the unnamed statement instead, which PgBouncer forwards untouched and PostgreSQL re-plans on every Bind. PgDoorman rewrites the unnamed statement to a named DOORMAN_<N> and shares it across clients. If your application uses one of those drivers, that is the paragraph to read twice.

The limitations are listed, to the project’s credit, on its own comparison page: no LDAP, no replication connections, no protocol 3.2 negotiation, no direct TLS, no SCRAM channel binding. The public history is short, and some features (a JWT scheme called Talos) are labeled Ozon-specific.

The extras are aimed at people who run Patroni. PgDoorman can ask the Patroni REST API where to go when its local node fails, ships a small TCP proxy that routes by role with a replica lag limit, and can replace its own binary without dropping client sessions, TLS ones included. It also has a Prometheus endpoint with latency percentiles and a web console.

ProxySQL: the MySQL proxy learns a second language

ProxySQL has been the default proxy in the MySQL world for about a decade. PostgreSQL support arrived in 3.0.0-alpha on September 30, 2024. Version 3.0.1 (May 2025) added the session parameter tracking that multiplexing depends on, and 3.0.3 (November 2025) added the extended query protocol, which is to say prepared statements, which is to say most drivers. The current stable release is 3.0.11, announced September 5, 2026. The license is GPLv3.

It is multi-threaded C++, with pgsql-threads defaulting to 4, listening for PostgreSQL clients on port 6133. Configuration is not a file. It is a set of tables behind an admin interface (port 6132, and you connect with psql), with three layers: you edit in memory, LOAD to runtime, and SAVE to disk. This is either the best idea in the article or deeply strange, depending on whether you came from MySQL.

ProxySQL calls transaction pooling multiplexing. It parses SET and tracks the result per session, through savepoints and rollbacks, and it stops multiplexing a connection when it sees something it cannot move: a temporary table, a session-level advisory lock, LOCK TABLE. It finds those by comparing the start of the query text against strings like CREATE TEMP TABLE and SELECT pg_advisory_lock, which is cheaper than a parser and exactly as thorough as that sounds.

The limitation is youth. The PostgreSQL side is two years old, the extended protocol under one, and the documentation is still MySQL-first (the page on multiplexing does not mention PostgreSQL). Read/write splitting is done by regular-expression rules you write, not by parsing.

What you get in exchange is the query rule engine: match a query by pattern or user, then route it to a hostgroup, rewrite it, cache its result, mirror it or block it. ProxySQL also monitors backends and moves servers between writer and reader hostgroups when pg_is_in_recovery() changes its answer. If you already run ProxySQL in front of MySQL, running it in front of PostgreSQL too is a reasonable thing to want. I would not adopt it for PostgreSQL alone yet.

The pooler your cloud already runs

RDS Proxy got a full section last time. The other providers each have something, and it is nearly always the same something.

Service What you get Where Catch
Azure Database for PostgreSQL PgBouncer 1.25.2, enabled with pgbouncer.enabled Same VM as the database, port 6432 Not on the Burstable tier; restarts with the server
Cloud SQL Managed Connection Pooling, transaction (default) or session Port 6432 Enterprise Plus edition only; enabling it restarts the database
AlloyDB Managed connection pooling, generally available since January 2026 Port 6432 Same transaction-mode restrictions
Neon PgBouncer, up to 10,000 client connections Add -pooler to the hostname Migrations and LISTEN need the direct hostname
Supabase Shared Supavisor; dedicated PgBouncer on paid plans Port 6543 for transaction mode Transaction mode without prepared statements

Google does not say what its pooler is built on. The list of things that do not work in transaction mode (SET, LISTEN, WITH HOLD cursors, PREPARE, session advisory locks, LOAD) is PgBouncer’s feature table nearly row for row, and one of the settings is called max_prepared_statements. Draw your own conclusions.

Two things follow. First, everything the first article said about PgBouncer applies to these services, at whatever version the provider has reached. Azure’s 1.25.2 predates 1.26.0, so tracking search_path on PostgreSQL 18 is not yet the default there. Second, a pooler on the database’s own VM shares its CPU and its fate. Microsoft’s documentation says as much: deployed this way, PgBouncer “becomes a potential single point of failure,” and after a failover your clients reconnect. That is the trade RDS Proxy makes the other way, with separate infrastructure and a separate bill.

Pooling at the edge

Serverless functions cannot hold a connection open between invocations, which produced a category of pooler that lives in the function platform instead of beside the database. Cloudflare Hyperdrive keeps a pool in the regions closest to your database and hands it to Workers. It runs in transaction mode, resets the connection on return so that a SET does not outlive its transaction, supports named prepared statements from postgres.js and node-postgres, and can cache query results. Prisma Accelerate is the same idea for Prisma’s ORM, with queries carried over HTTP.

Neither is a general-purpose pooler. Each is the answer if you are on that platform, and not otherwise.

The pool you already have

Every serious driver stack has a pool in it: HikariCP, pgxpool, psycopg_pool, SQLAlchemy, Npgsql, ActiveRecord. From the database’s side this is session pooling done by the client. The connections stay open, the application’s threads take turns, and nothing is restricted, because each connection is still one real session.

If you have eight application servers that stay up for weeks, with a pool of ten each, you have 80 connections and no need for anything in this article or the last one. A proxy would add a hop and a set of restrictions and take away nothing.

The application pool stops being enough when the number of processes stops being small or stops being fixed. Autoscaling to 200 pods multiplies every pool by 200. Runtimes that fork a worker per core multiply it again. Nobody coordinates the total, because each pool can only see itself. That is the point at which you need a proxy doing transaction pooling, and not before.

When you add one, keep the application pool. Advice to turn it off behind PgBouncer (SQLAlchemy’s NullPool is the usual form) is common and usually wrong: without it, every request becomes a new login to the pooler, and TLS handshakes and SCRAM exchanges are among the most expensive things a single-threaded PgBouncer does. PgBouncer 1.26.0 added a client_login_count statistic for finding clients that do this.

Do check what the application pool sends when it returns a connection. A pool configured to run DISCARD ALL on release is harmless against PostgreSQL, pointless against PgBouncer in transaction mode, and enough to pin every session on RDS Proxy.

Not yet, and not anymore

Multigres is Supabase’s adaptation of Vitess for PostgreSQL, from Sugu Sougoumarane, who co-created Vitess. Version 0.1 alpha came out on June 4, 2026, with a “multigateway” that accepts clients and a “multipooler” that manages server connections. Its pooler has no mode to choose; like PgDog, it parses what the client sends and works out what can be shared. The announcement says, in so many words, “It is not yet ready for production workloads,” and the sharding that is the reason for the project is not in this release. Watch it.

And then there is the question someone always asks: why is this not in PostgreSQL? PostgreSQL 19 is at Beta 4 and has no built-in pooler. Konstantin Knizhnik of Postgres Professional posted a patch for one to pgsql-hackers in January 2019, with backends that became dedicated to a client once it touched session state. It was not committed. Postgres Pro shipped the feature in its Enterprise product, where the documentation now reads: “The built-in connection pooler functionality is deprecated. Use the pgbouncer extension or another external tool instead.”

When the vendor with the built-in pooler tells you to use PgBouncer, the matter is settled for now.

The scorecard, continued

Pooler Since Runs as Pooling modes SET in transaction mode Beyond pooling
Odyssey 2016 Multi-threaded (C) Session, transaction Remembered and re-applied Routing by server role, online restart, LDAP
pgagroal 2019 One process per client (C) Session, transaction Leaks; nothing is reset Failover script, prefill
Supavisor 2023 Cluster of nodes (Elixir) Session, transaction, native As PgBouncer Multi-tenancy
PgDoorman 2025 Multi-threaded (Rust) Session, transaction Reset at check-in Patroni fallback, live binary upgrade
ProxySQL 2024 for PostgreSQL Multi-threaded (C++) Multiplexing on or off Parsed and tracked Query rules, routing, caching, mirroring

Which one, continued

If one PgBouncer process is not enough and you would rather not run four behind so_reuseport, look at Odyssey and PgDoorman. Odyssey has the longer public record and the wider choice of authentication. PgDoorman is the better fit for drivers that use unnamed prepared statements and for clusters run by Patroni. If you are leaving PgCat and wanted a pooler, not a sharding layer, PgDoorman is the shorter move.

If you are on a managed service, use the pooler it gives you, find out which version of PgBouncer it is, and find out what happens to it during a failover before the failover.

If you host databases for other people by the thousand, Supavisor. If you already run ProxySQL, ProxySQL. If you want session pooling with one process per client, pgagroal.

And if your application servers are few and long-lived, use the pool in your driver and close this tab.