Making pg_regress the Bar: Getting a Connection Pooler to 100% on PostgreSQL's Own Test Suite
Presented by:
Manan Gupta
No video of the event yet, sorry!
A connection pooler is a lie you tell your application. The client thinks it has a PostgreSQL backend all to itself; in reality it is being multiplexed onto a shared pool. Every Postgres behaviour that quietly depends on which backend you are on is a chance for that lie to leak: GUCs, prepared statements, temp tables, advisory locks, LISTEN/NOTIFY, query cancellation, COPY, and the extended query protocol. Most poolers handle this by narrowing the contract - don't use session state, don't use prepared statements, use session pooler instead. We wanted to know what it would take to not narrow it at all.
So we picked a bar that isn't ours to move: PostgreSQL's own regression suite. Not a subset, not a smoke test, but actual pg_regress, pg_isolation_regress tests, the contrib suites, and the test suites of real extensions like PostGIS, pgvector, pg_graphql, pgTAP and pg_cron, all pointed at the pooler instead of at Postgres.
This talk is about what the pooler can do and how we test it, not how it's built. At PGConf.dev 2026 I gave Connection Pooling Beyond PgBouncer: A Custom Approach for Distributed PostgreSQL, which went into detail on how all of it is implemented; this is the other half of that story, how we convinced ourselves it was actually true. The organising principle is that PostgreSQL is the oracle: every suite runs the same input against real Postgres and against the pooler, and diffs. That technique is the portable part, and it's the reason to come even if you never touch our pooler. It applies to anything that sits between an application and Postgres and claims to be transparent.
Reaching 100% is the spine of the talk, and the last few percent is where the talk actually lives. A taste of what hides there: on Postgres, SELECT 1/0 under the extended protocol fails at Bind, because planning is eager. Through a pooler that plans lazily, the identical client sees it fail at Execute. Same error, arriving on a different protocol message, and any driver with opinions about where errors show up will notice.
Along the way: why we build Postgres from source to run its own tests, why expected-output diffs are so locale- and platform-sensitive that patch regeneration had to move into a container, how we normalize away backend PIDs and error cursors without normalizing away real bugs, and the test that proves session state doesn't bleed across pooled backends by deliberately trying to make it bleed.
- Date:
- Duration:
- 40 min
- Room:
- Conference:
- PG Down Under 2026
- Language:
- Track:
- Development
- Difficulty:
- Hard