Connection pooling
Opening a fresh DB connection per request is expensive, and databases cap how many they'll accept.
Open the interactive version → diagrams, practice & moreThe problem
Opening a fresh DB connection per request is expensive, and databases cap how many they'll accept.
The idea
Reuse a pool of pre-opened connections instead of creating one each time.
How it works
A pooler (PgBouncer or an in-app pool) keeps N warm connections and hands them out, removing per-request setup and capping concurrency at the DB. Sizing is counter-intuitive — more connections ≠ more throughput. Each Postgres connection is a backend process with real memory; beyond ~(cores × 2–4) they mostly context-switch and contend, so a small pool often beats a large one. Transaction-mode pooling multiplexes many clients onto few server connections — essential for serverless, where thousands of function instances would each open connections.
The tradeoff
Too small a pool throttles throughput and queues requests; too large overwhelms the DB with context-switching and memory pressure, and can deadlock when every connection waits on another. The sweet spot is small and tuned to DB cores, not client demand — and a transaction-mode pooler decouples client count from server connections entirely.
In the wild
Serverless functions infamously exhaust DB connections without a pooler in front.
Interview deep dive
Flow
- Put a pooler (PgBouncer/in-app) in front of the database.
- Size the pool near DB cores (×2–4), not client count.
- Use transaction-mode pooling for serverless / high client fan-out.
- Monitor wait time and saturation; tune rather than enlarge.
Watch for
- More connections ≠ more throughput — they context-switch and contend.
- Serverless without a pooler exhausts DB connections instantly.
- A pool where every connection waits on another can deadlock.
Interviewer trap
Size the pool to DB cores and name transaction-mode pooling for serverless connection storms.