Why Pools Matter
Each PostgreSQL connection costs:
- ~5-10MB of server RAM
- A dedicated process
- TCP setup overhead
At 10,000 concurrent connections, PostgreSQL might hit memory limits or process limits before request limits.
Connection pools let you reuse a fixed set of connections across many application instances.
Pool Configuration
Using pg library for Node.js:
const { Pool } = require('pg');
const pool = new Pool({
max: 20, // Max connections to keep open
min: 5, // Min idle connections to maintain
idleTimeoutMillis: 30000, // Idle connection TTL (30s)
connectionTimeoutMillis: 5000, // Timeout waiting for available connection
});
Tuning for Load
Rule of Thumb
Pool Size = (Num Cores × 2) + Effective Spindle Count
For a 4-core machine: (4 × 2) + 0 = 8 connections.
For databases with SSD: use 2 × cores = 8 connections.
Under Load: Requests Timeout
Symptom: "getConnection() timeout exceeded"
const pool = new Pool({
max: 20,
min: 10, // Increase min idle connections
connectionTimeoutMillis: 10000, // Increase timeout to 10s
});
Monitor connection utilization:
setInterval(() => {
console.log(`Pool: ${pool.totalCount} total, ${pool.idleCount} idle`);
if (pool.idleCount === 0) {
console.warn('Pool exhausted, increasing max');
}
}, 5000);
Idle Connections Pile Up
Symptom: select count(*) from pg_stat_activity shows 1000+ connections.
Culprit: Long idleTimeoutMillis or applications that never close connections.
const pool = new Pool({
idleTimeoutMillis: 10000, // Reduce from 30s to 10s
connectionTimeoutMillis: 3000, // Aggressive timeout
statement_timeout: '30s', // Database-side timeout (prevent hanging queries)
});
Query Hangs Block the Pool
If a query hangs, the connection is held until timeout. Every waiting request consumes memory and a slot from the pool.
// Add timeout to queries
const result = await pool.query(
{ text: 'SELECT * FROM large_table', timeout: 5000 },
[userId]
);
Monitoring
Key metrics:
- Connections active: number of queries in progress
- Connections idle: available connections
- Queue depth: requests waiting for a connection
- Connection latency: time to acquire a connection
Example Prometheus metrics:
const prometheus = require('prom-client');
const poolTotal = new prometheus.Gauge({
name: 'pg_pool_total_connections',
help: 'Total connections in pool',
});
const poolIdle = new prometheus.Gauge({
name: 'pg_pool_idle_connections',
help: 'Idle connections available',
});
setInterval(() => {
poolTotal.set(pool.totalCount);
poolIdle.set(pool.idleCount);
}, 5000);
Checklist
- Set max = 2 × CPU cores as baseline
- Set min = 1/2 of max (reduce startup time)
- Set idleTimeoutMillis = 10–30s (avoid idle connection waste)
- Set connectionTimeoutMillis = 5s (fail fast on pool exhaustion)
- Add database statement_timeout (prevent hanging queries from blocking pool)
- Monitor pool.totalCount and pool.idleCount in production
- Alert when idleCount === 0 (pool exhaustion)
- Set up slow query logging (queries > 1s)