πConnection pooling
Sessions are expensive
Each new connection pays TCP, TLS, authentication, and server-side session setup. Pools multiplex application threads onto a smaller set of persistent sessions, mis-size them and you either starve work (too few) or melt RAM and max_connections (too many). Idle-in-transaction sessions are especially toxic.
πCommon poolers
Know transaction vs statement modes.
| Pooler | Notes |
|---|---|
| PgBouncer | Transaction or statement pooling, changes how prepared statements and session state behave. |
| RDS Proxy / vendor proxies | IAM auth plus multiplexing for Lambda and microservices; watch credential rotation semantics. |
Observe connection state (PostgreSQL)
SELECT state, COUNT(*) AS n
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY state;| state | n |
|---|---|
| active | 12 |
| idle | 88 |
Pool sizing by pod count
- Serverless without a proxy, connection storms on cold start.
- Holding DB transactions open while awaiting HTTP calls, pool slots stuck idle but busy.
- One superuser pool shared by every service, blast radius and noisy neighbor risk.
Capacity planning
- Align pool max with CPU, working set, and p99 statement time, not βone connection per goroutine.β
- Statement timeouts and idle session killers as guardrails.
- Separate pools for OLTP vs batch where the stack allows; monitor checkout wait time.
Prepared statements
Some pool modes break session-pinned prepared plans, validate extended query protocol behavior with your exact driver + pooler combo instead of assuming localhost defaults.
TL;DR
Connection pooling reuses TCP sessions to the database so opening a new HTTP request does not imply a new login + handshake every time. Tune pool size to CPU and max connections on the server; too few connections starves throughput, too many creates context switching and lock contention. Poolers like PgBouncer add transaction vs session pooling modes that affect prepared statements, temp tables, and SET: match mode to what your app holds on the session.