"Timeout acquiring a connection" under burst load: should pool.min be 0 or positive in Knex?
0 reputation · 30 Apr 2026, 12:20 UTC
0 reputation · 30 Apr 2026, 12:20 UTC
Under burst load my Knex queries intermittently reject with TimeoutError: Knex: Timeout acquiring a connection. The pool is probably full. Are you missing a .transacting(trx) call? I have confirmed I am passing trx correctly inside transactions, so the remaining suspect is pool sizing via tarn.js.
The trade-off I cannot resolve: with pool.min: 0, every cold start must create a connection, and under a sudden burst those creations can exceed createTimeoutMillis or serialize badly. With a positive min, idle connections stay open indefinitely, and with several app instances against a hosted Postgres with a low max_connections, the idle connections alone can exhaust server capacity. I have also read that connections killed by the database (e.g. MySQL wait_timeout) should be validated on checkout and replaced, but that this behavior is version-sensitive.
Assume Knex 2.x/3.x with tarn-backed pooling against Postgres.
pool.min relative to instance count and database max_connections?min pool from handing out connections the server already killed, in current Knex versions?35275 reputation · 30 Apr 2026, 13:44 UTC
Set pool.min to a small constant (2–5) per instance, not 0 and not a fraction of max_connections. Enable testOnConnect (or validationQuery) so tarn validates on every checkout. Log acquire, create, and error pool events with timestamps to distinguish cold-start creation timeouts from genuine exhaustion.
There is no single documented formula, but the widely used heuristic is:
max_connections.pool.max at a modest multiple (e.g., 10–20) so burst headroom exists without starving other instances.max_connections = M, ensure N × pool.max < M with headroom for superuser connections and other clients.With min = 0, a sudden burst forces serial connection creation; each create must finish within createTimeoutMillis (default 30 s). Under load, creation can queue and exceed that timeout. A small min keeps a few connections warm so the first acquisitions succeed instantly.
In Knex 2.x/3.x (tarn 3.x), testOnConnect (or validationQuery: 'SELECT 1') runs on every checkout when the connection has been idle. It reliably replaces connections the server killed (e.g., Postgres idle_in_transaction_session_timeout or manual termination).
Caveats:
Attach listeners to the underlying tarn pool (available via knex.client.pool after initialization):
const pool = knex.client.pool;
pool.on('acquire', (conn) => {
conn._acquiredAt = Date.now();
});
pool.on('release', (conn) => {
const waited = Date.now() - conn._acquiredAt;
if (waited > 100) console.warn({event: 'slow-release', waited});
});
pool.on('create', () => console.info({event: 'create', ts: Date.now()}));
pool.on('error', (err) => console.error({event: 'pool-error', err: err.message}));
pool.on('acquireTimeout', () => console.error({event: 'acquireTimeout', ts: Date.now()}));
Correlate these logs with createTimeoutMillis:
create events spike, acquisition duration approaches/exceeds createTimeoutMillis, few or no acquireTimeout before the burst.create rate is stable, acquireTimeout errors rise while pool size sits at max, wait times grow linearly with queue depth.What is your current createTimeoutMillis and acquireTimeoutMillis? If createTimeoutMillis is left at the default 30 s, raising it to 60–90 s often eliminates cold-start timeouts without changing min.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.