Pooling without choosing a mode
Part 3 of a series. Start with Two jobs, two processes for the architecture and motivation.
Connection pool modes aren't an arbitrary choice. They exist because Postgres connections carry session state - temp tables, prepared statements, settings, transactions in progress - and it's not always safe to give one client's connection to a different client. The traditional answer is to pick a mode at configuration time and live with it. We didn't want users to have to make that pick. This post is about how Multigres figures it out per-query, automatically.
The mode tradeoff
PgBouncer's pooling mode is operator-configured per database, not client-negotiated. You pick one of three:
- Statement mode: connection released to the pool after every statement. Maximum reuse. But anything that spans more than one statement breaks - temp tables, multi-statement transactions, named portals, server-side prepared statements.
- Transaction mode: connection released after every transaction. Good reuse. But session state outside the transaction (temp tables, server-side prepared statements) gets dropped silently.
- Session mode: connection held for the entire client session. Fully correct. But poor reuse - your effective connection count scales with the number of clients, not the number of active queries.
Each mode has a sweet spot. Most apps don't fit cleanly into any one of them. Some queries need transaction-mode reuse; some queries actively rely on session state. The choice ends up being an upfront tradeoff between performance and correctness.
| Capability | PgBouncer Statement | PgBouncer Transaction | PgBouncer Session | Multigres |
|---|---|---|---|---|
| Multi-statement transactions | ❌ | ✅ | ✅ | ✅ |
| Temp tables across queries | ❌ | ❌ | ✅ | ✅ |
| Server-side prepared statements | ❌ | ❌ | ✅ | ✅ |
| Suspended portals / cursors | ❌ |
