Knex.js and an externally managed pg.Pool: who owns transaction connection acquisition?
0 reputation · 09 Jan 2020, 09:55 UTC
0 reputation · 09 Jan 2020, 09:55 UTC
I am setting up a repeatable development environment for a Node.js service backed by PostgreSQL, and I need to decide how Knex.js (assume a current 1.x/2.x line) should share connection management with the underlying pg driver.
My understanding is that when no pool option is given, Knex builds its own generic-pool wrapper (default min 2, max 10), but when I pass a pre-created pg.Pool instance via the pool config key, Knex delegates acquisition to it entirely. I want one shared pool across the app so connection counts stay predictable against a small local Postgres instance, and I want transactions to hold exactly one connection for their full duration.
What is unclear to me is the contract at this boundary: whether passing an external pool risks any double-pooling behavior, whether Knex's transaction logic still guarantees single-connection checkout when the pool is externally owned, and whether introspecting pool state through knex.client.pool is stable enough to rely on for a dev-environment health check, given that its shape appears to differ depending on configuration and possibly version.
Specifically:
pg.Pool a documented, supported way to make Knex delegate pooling, or is it an internal seam?knex.transaction() still pin one connection for the transaction's lifetime?knex.client.pool structure?35275 reputation · 09 Jan 2020, 12:47 UTC
Knex.js does not support passing a pre-created pg.Pool instance via the pool configuration key. The pool option accepts only a plain options object (min, max, acquireTimeoutMillis, etc.) that Knex feeds to its internal tarn.js pool. Knex always owns the pool; it never delegates acquisition to an external pg.Pool.
client: 'pg' tells Knex to use the pg driver for raw connections.connection takes a connection config object or a connection string — not a pg.Pool instance.pool takes a tarn options object (e.g., { min: 2, max: 10 }). Knex instantiates a tarn pool that calls pg.Client directly for each connection.pg.Pool a documented, supported way?
No. The Knex configuration guide documents pool as a tarn options object. Passing a pg.Pool instance there is not a supported seam and will either be ignored or cause runtime errors.
knex.transaction() still pin one connection for the transaction's lifetime?
Yes — but via Knex's internal tarn pool. knex.transaction() calls client.acquireConnection(), which checks out a single pg.Client from the tarn pool and holds it until the transaction promise settles. This behavior is independent of any external pool you might create.
No public API exists. knex.client.pool exposes the internal tarn pool instance, but its shape is considered private and may change between Knex versions. For a dev-environment health check, query pg_stat_activity directly or monitor the PostgreSQL server's connection count.
If you want one predictable pool across the application, configure Knex's built-in pool once and reuse the Knex instance:
const knex = require('knex')({
client: 'pg',
connection: {
host: 'localhost',
port: 5432,
user: 'devuser',
password: 'devpass',
database: 'devdb'
},
pool: { min: 2, max: 5 } // tarn options; adjust max for your local Postgres
});
module.exports = knex; // singleton for the app
This gives you a single tarn pool with a known connection ceiling. All transactions and queries draw from it.
knex.client.pool will be a tarn pool (has numUsed, numFree, acquire, release). It is not a pg.Pool.SELECT pg_sleep(5) and, in another terminal, run SELECT pid, state FROM pg_stat_activity WHERE datname='devdb';. You will see exactly one backend connection in active state for the transaction duration.pg.Pool alongside Knex. If you do, you will have two independent pools (Knex's tarn pool + your pg.Pool), each with its own connection limits.pg.Pool to connection or pool — it is not a valid value for either key.pg.Pool and letting Knex create its own pool unless you intentionally want two separate pools.knex.client.pool for production metrics or health checks; treat it as an implementation detail.Are you trying to share connections between Knex and non-Knex code (e.g., a legacy module that uses pg directly)? If so, the only supported pattern is to let each own its pool, or to run a single Knex instance and pass its client to the legacy code for raw pg.Client access via knex.client.acquireConnection() / releaseConnection().
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.