PgBouncer vs Odyssey vs PgDog: Which Postgres Pooler?

PgBouncer vs Odyssey vs PgDog: Which Postgres Pooler?

Everyone thinks they know what a PostgreSQL connection pooler is for: making the database faster. We spent a day deploying three of them against an identical database and taking 104 measurements on a 4 vCPU, 8 GB machine on 8 September 2026. The first thing we found is that the premise is wrong, and the real answer is more interesting than the one we went looking for.

Three candidates. PgBouncer, sixteen years old, written in C, the historical reference. Odyssey, built by Yandex, multi-threaded, eight years in the field. PgDog, twenty-one months old, written in Rust, aiming to be a query router and a sharder as well as a pooler.

How we measured

Measured on 8 September 2026 against PgBouncer 1.25.2 and Odyssey 1.5.1. PgDog was a pre-1.0 build from that same week; the project ships weekly and we did not pin a tag, so treat its figures as a snapshot rather than a standing result. PgBouncer has since released 1.26.1 and Odyssey 1.5.2.

Most pooler comparisons are useless because they compare whole stacks: three poolers, three databases, three cache states. At that point you are not measuring poolers any more.

We used a single dedicated PostgreSQL 17 instance, external to all three installations: 307 MB of pgbench data, max_connections = 300, capped at two cores and two gigabytes. All three poolers connected with strictly aligned settings, pool size 20, transaction mode. Measurements ran sequentially, one pooler at a time with the other two stopped, because three concurrent poolers on four cores would have measured contention rather than poolers. Every figure is the median of three runs after a warm-up, and we included a no-pooler baseline. Without that baseline you cannot tell what pooling buys you.

What pooling actually does

Start with the result that overturned our expectations. The churn test simulates the worst case: open a connection, run one query, close it, repeat. That is legacy PHP, a serverless function, a badly written cron job. It is supposed to be where poolers shine.

Direct, with no pooler, we got 229 connections per second. Through PgBouncer, 354. Fifty percent better, which is decent and nothing more. You could stop there and conclude pooling brings little. We nearly did.

The answer was somewhere else entirely:

Churn test, 50 clients Database CPU Pooler CPU
Direct, no pooler 138.5% n/a
Through PgBouncer 2.7% 4.9%

The database goes from nearly saturated to essentially idle. A factor of fifty. Every direct connection forces PostgreSQL to fork a process, initialise it, negotiate authentication and tear it down, and at that rate that is all it does. With a pooler in front, PostgreSQL never sees those connections. It holds twenty open permanently and makes everyone queue on those, absorbing the storm for 4.9% of one core.

A pooler does not make your application faster. It makes your database indifferent to what your application does. Behind two similar throughput numbers sit a database on the edge of collapse and a database at rest.

The cost nobody mentions

Second profile: persistent connections running back-to-back reads, which is what a normal application looks like.

Clients Direct PgBouncer PgDog Odyssey
10 75,948 48,816 40,249 25,377
50 57,907 47,301 42,549 22,687
100 52,865 45,056 42,136 22,682

Two things stand out. A pooler is expensive at low concurrency: at ten clients direct access crushes everyone, 76,000 against 48,000 for the best pooler. A pooler is a middleman, it adds a round trip and a packet copy. If your application opens ten connections and keeps them, a pooler costs you 35% of your throughput.

And direct access collapses while poolers hold. Between 10 and 100 clients, direct loses 30% of its throughput. PgBouncer loses 8%. PgDog loses nothing and even climbs slightly. The gap narrows from 27,000 transactions to 8,000, with the slope asking to invert beyond that.

You do not install a pooler to go faster. You install one to avoid slowing down.

The multi-threading myth

PgBouncer is single-threaded, which is the number one selling point of both competitors. So we varied the worker count at fifty clients. Odyssey gained 1% between one and four threads. PgDog gained 10% going to two workers, then regressed at four as it started fighting the database and the load generator for cores. Single-threaded PgBouncer, at 47,758 transactions per second, stayed ahead of both.

That needs an honest caveat: it is true on this machine. Four cores, two reserved for the database. Parallelism has nothing to parallelise here. On a sixteen or thirty-two core box with a database able to keep up, the ranking would very likely invert. The portable lesson is narrower: on a small VM, setting a multi-threaded pooler to "all cores" is a bad idea. PgDog's optimum here was two workers, not four.

Footprint

50 clients CPU Memory Throughput per % CPU
PgBouncer 97.6% 2.1 MB 434
PgDog 163.9% 7.0 MB 248
Odyssey 119.3% 35.7 MB 176

The last column is the one that matters. PgBouncer delivers 2.5 times more throughput per unit of CPU than Odyssey. And 2.1 MB of memory against 35.7, a factor of seventeen. Sixteen years of optimisation in C show.

One nuance: PgBouncer is at its ceiling. 97.6% is one saturated core and it will not go higher. PgDog burns 164% precisely because it can spread across cores. Maximum efficiency and room to grow are not the same bet.

What installation told us

Deploying software often tells you more than its documentation does.

PgBouncer has no living official Docker image. The upstream image has been frozen since 2020 on version 1.15 while the current release is 1.26.1, and Bitnami emptied its public tags in 2025. What remains is a well-maintained community image. For the reference implementation of the field, that is an awkward paradox.

Odyssey 1.5.1 cannot authenticate with SCRAM over an encrypted link. On a TLS connection PostgreSQL advertises SCRAM-SHA-256-PLUS first, Odyssey reads only the first entry, does not understand it and gives up. Fixed upstream in June 2026, and at the time of our run no published release carried the fix, which meant the shipping Odyssey could not pool a remote managed database over TLS. That has since changed. Odyssey 1.5.2 shipped on 13 September 2026, five days after this benchmark, and its release notes list a correct SCRAM passthrough implementation among the long-standing fixes. The problem described here belongs to 1.5.1, the release that existed when we measured.

PgDog negotiated TLS automatically against the same self-signed certificate with no configuration at all. It was the only one of the three to pass that case end to end.

So which one?

PgBouncer if your problem is a connection problem and nothing else. Most efficient per CPU cycle by a distance, by far the lightest, the most battle-tested. You will live with a community Docker image, and statement mode breaks transactions.

PgDog if you want more than a pooler, or if you need to pool a remote encrypted database. It is the only one that routes queries and shards, the only one that handles TLS to a managed database properly, the only one with headroom when cores are available. In exchange you take pre-1.0 software shipping weekly, an AGPL license and an open-core model.

Re-test Odyssey before you decide. On the version we measured it was slowest of the three, with the heaviest memory footprint and the lowest efficiency per cycle, and its published release could not reach a remote database over TLS. Both findings belong to 1.5.1. Version 1.5.2 has since shipped with the SCRAM fix and with performance work of its own, so these numbers no longer describe the current release. The engineering was serious and the project is active, which is exactly why we are not carrying a September verdict forward unchanged.

And if your application opens ten connections and holds them, install none of them. You would give up 35% of your throughput solving a problem you do not have.

Where this benchmark stops

One modest machine, four virtual cores with two given to the database. That sizing structurally disadvantages multi-threaded poolers, and the sustained-load ranking could invert on a large box. The footprint and stability numbers hold anywhere. One query profile, simple reads, no heavy writes or long transactions. Runs of twelve to fifteen seconds, three repetitions: enough to separate a factor of two, not enough to settle two configurations five percent apart. The load generator shared the machine, identically across all four configurations. And PgDog's sharding and query routing need a multi-node topology this benchmark did not have, so they are listed rather than proven.

All three poolers, and the managed PostgreSQL they sit in front of, are available on Elestio with backups, SSL and updates handled.

Thanks for reading ❤️ See you in the next one 👋