Database Connection Pool Manager
Optimize database connection pools for throughput, latency, and resource efficiency using PgBouncer, application-level poolers, and cloud-managed pools.
Activation Triggers
Activate on: "connection pool", "PgBouncer", "database connections", "pool size", "connection limit", "too many connections", "connection timeout", "Prisma pool", "Supabase pooler"
NOT for: SQL query optimization → data-warehouse-optimizer | Schema design → dimensional-modeler | ORM selection → api-architect
Quick Start
- Audit current connections —
SELECT count(*) FROM pg_stat_activity to understand baseline
- Choose pooling mode — transaction pooling (default), session pooling (for prepared statements)
- Size the pool — start with
connections = (cores * 2) + spindle_count per PostgreSQL docs
- Deploy pooler — PgBouncer sidecar or Supabase/Neon built-in pooler
- Monitor — track active/idle/waiting connections, query queue time
Core Capabilities
| Domain |
Technologies |
| External Poolers |
PgBouncer 1.23+, Odyssey, PgCat |
| Cloud Poolers |
Supabase Supavisor, Neon pooler, RDS Proxy |
| App-Level |
Prisma connection pool, Drizzle pool, node-postgres Pool |
| Monitoring |
pg_stat_activity, PgBouncer SHOW commands, Prometheus |
| Databases |
PostgreSQL 16+, MySQL 8.4+, CockroachDB |
Architecture Patterns
PgBouncer Transaction Pooling
App Instances (100 connections)
↓
PgBouncer (pool_mode = transaction)
max_client_conn = 200
default_pool_size = 20
reserve_pool_size = 5
↓
PostgreSQL (max_connections = 30)
Key: 200 app connections share 20 actual database connections. Each connection is released back to the pool at transaction end.
Pool Sizing Formula
Optimal pool size = ((core_count * 2) + effective_spindle_count)
Example (8-core server, SSD):
pool_size = (8 * 2) + 1 = 17
For serverless (many short-lived functions):
pgbouncer.default_pool_size = 20
pgbouncer.min_pool_size = 5
app.max_pool_size = 5 (per function instance)
total_functions * 5 <= pgbouncer.max_client_conn
Prisma with PgBouncer
// schema.prisma — pgbouncer mode disables prepared statements
datasource db {
provider = "postgresql"
url = env("DATABASE_URL") // pooler:6543/db?pgbouncer=true
directUrl = env("DIRECT_DATABASE_URL") // direct:5432/db (for migrations)
}
// Connection limit per Prisma instance
generator client {
provider = "prisma-client-js"
}
// At runtime
const prisma = new PrismaClient({
datasources: {
db: { url: process.env.DATABASE_URL },
},
// connection_limit set via URL param: ?connection_limit=5
});
Anti-Patterns
- One connection per request — never open/close connections per HTTP request; use a pool
- Oversized pools — more connections != more throughput; past the optimal size, context switching kills performance
- Session pooling by default — use transaction pooling unless you need prepared statements or session variables
- No connection timeout — always set
idle_timeout and server_idle_timeout to reclaim stale connections
- Ignoring pool exhaustion — monitor
cl_waiting in PgBouncer; if clients wait, pool is undersized or queries are too slow
Quality Checklist
1---2name: database-connection-pool-manager3description: PgBouncer, connection optimization, and pooling strategies for database performance. Activate on: connection pool, PgBouncer, database connections, pool size, connection limit, Prisma pool, Drizzle pool. NOT for: query optimization (use data-warehouse-optimizer), database schema design (use dimensional-modeler).4license: Apache-2.05---67# Database Connection Pool Manager89Optimize database connection pools for throughput, latency, and resource efficiency using PgBouncer, application-level poolers, and cloud-managed pools.1011## Activation Triggers1213**Activate on:** "connection pool", "PgBouncer", "database connections", "pool size", "connection limit", "too many connections", "connection timeout", "Prisma pool", "Supabase pooler"1415**NOT for:** SQL query optimization → `data-warehouse-optimizer` | Schema design → `dimensional-modeler` | ORM selection → `api-architect`1617## Quick Start18191. **Audit current connections** — `SELECT count(*) FROM pg_stat_activity` to understand baseline202. **Choose pooling mode** — transaction pooling (default), session pooling (for prepared statements)213. **Size the pool** — start with `connections = (cores * 2) + spindle_count` per PostgreSQL docs224. **Deploy pooler** — PgBouncer sidecar or Supabase/Neon built-in pooler235. **Monitor** — track active/idle/waiting connections, query queue time2425## Core Capabilities2627| Domain | Technologies |28|--------|-------------|29| **External Poolers** | PgBouncer 1.23+, Odyssey, PgCat |30| **Cloud Poolers** | Supabase Supavisor, Neon pooler, RDS Proxy |31| **App-Level** | Prisma connection pool, Drizzle pool, node-postgres Pool |32| **Monitoring** | pg_stat_activity, PgBouncer SHOW commands, Prometheus |33| **Databases** | PostgreSQL 16+, MySQL 8.4+, CockroachDB |3435## Architecture Patterns3637### PgBouncer Transaction Pooling3839```40App Instances (100 connections)41 ↓42PgBouncer (pool_mode = transaction)43 max_client_conn = 20044 default_pool_size = 2045 reserve_pool_size = 546 ↓47PostgreSQL (max_connections = 30)48```4950Key: 200 app connections share 20 actual database connections. Each connection is released back to the pool at transaction end.5152### Pool Sizing Formula5354```55Optimal pool size = ((core_count * 2) + effective_spindle_count)5657Example (8-core server, SSD):58 pool_size = (8 * 2) + 1 = 175960For serverless (many short-lived functions):61 pgbouncer.default_pool_size = 2062 pgbouncer.min_pool_size = 563 app.max_pool_size = 5 (per function instance)64 total_functions * 5 <= pgbouncer.max_client_conn65```6667### Prisma with PgBouncer6869```typescript70// schema.prisma — pgbouncer mode disables prepared statements71datasource db {72 provider = "postgresql"73 url = env("DATABASE_URL") // pooler:6543/db?pgbouncer=true74 directUrl = env("DIRECT_DATABASE_URL") // direct:5432/db (for migrations)75}7677// Connection limit per Prisma instance78generator client {79 provider = "prisma-client-js"80}8182// At runtime83const prisma = new PrismaClient({84 datasources: {85 db: { url: process.env.DATABASE_URL },86 },87 // connection_limit set via URL param: ?connection_limit=588});89```9091## Anti-Patterns92931. **One connection per request** — never open/close connections per HTTP request; use a pool942. **Oversized pools** — more connections != more throughput; past the optimal size, context switching kills performance953. **Session pooling by default** — use transaction pooling unless you need prepared statements or session variables964. **No connection timeout** — always set `idle_timeout` and `server_idle_timeout` to reclaim stale connections975. **Ignoring pool exhaustion** — monitor `cl_waiting` in PgBouncer; if clients wait, pool is undersized or queries are too slow9899## Quality Checklist100101- [ ] Pool size calculated based on CPU cores, not arbitrary numbers102- [ ] PgBouncer or equivalent deployed for serverless/high-connection workloads103- [ ] Transaction pooling mode used (session pooling only when required)104- [ ] `idle_timeout` set to reclaim unused connections (default: 300s)105- [ ] Application `connection_limit` per instance is <= pool_size / instance_count106- [ ] Migrations run on direct connection, not through pooler107- [ ] Connection pool metrics exported (active, idle, waiting, total)108- [ ] Alert configured for pool exhaustion (waiting > 0 for > 10s)109- [ ] Prepared statements disabled when using transaction pooling110- [ ] Load tested: pool handles 2x expected concurrent connections