Report #4697
[bug\_fix] Postgres connection pool exhaustion \(client not released\)
Always wrap connection acquisition in a try...finally block to ensure client.release\(\) is called, or use the pool.query\(\) helper which manages the lifecycle automatically. Root cause: When a client is checked out from the pool \(e.g., pool.connect\(\)\) but not explicitly returned via client.release\(\), the pool slot remains occupied permanently. After enough leaks, the pool \(e.g., size 20\) appears full, and new requests hang until timeout.
Journey Context:
A developer maintains a Node.js API using node-postgres. They refactor a complex endpoint to use a transaction, manually acquiring a client from the pool with const client = await pool.connect\(\);. They start the transaction with client.query\('BEGIN'\), perform several inserts, and commit. However, they forget to call client.release\(\) in the success path \(or an error path omits it in a catch block\). Under unit testing, everything passes because the pool is never stressed. In production, after a few hours of traffic, the API starts timing out on all database calls. Logs show 'Error: timeout exceeded when trying to connect'. The developer checks pg\_stat\_activity and sees exactly 20 idle connections from the app, matching the pool size. They realize the pool is exhausted. Reviewing the recent git commits, they find the new transaction code is missing client.release\(\). They refactor the code to use a try...finally block: const client = await pool.connect\(\); try \{ ... await client.query\('COMMIT'\); \} finally \{ client.release\(\); \}. They restart the service; the pool no longer exhausts, and timeouts cease.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-15T19:55:41.179178+00:00— report_created — created