The Connection Leak That Took Down Our Production Database

Published: (December 31, 2025 at 04:35 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

It was 3 AM. PagerDuty woke me up. Our API was returning 500 errors.
The database was fine. CPU was fine. Memory was fine. But every query was timing out.

FATAL: too many connections for role "app_user"

We had exhausted our 100‑connection limit even though traffic was normal. Where were all the connections going? After hours of debugging we found the culprit.

The connection leak hiding in our codebase

// ❌ Bad: missing client.release()
async function getUserOrders(userId) {
  const client = await pool.connect();
  const orders = await client.query('SELECT * FROM orders WHERE user_id = $1', [
    userId,
  ]);
  return orders.rows;
  // Where's client.release()? 🤔
}

Every call leaked a connection. With 50 requests/minute we exhausted the pool in 2 minutes.

Common leak scenarios

ScenarioResult
Forgot release() entirelyConnection never returned
Early return before release()Connection leaked
Exception thrownfinally block missing
Async errorUnhandled rejection, no cleanup

Fixing the leak

Always release in a finally block

// ✅ Good: release in finally
async function getUserOrders(userId) {
  const client = await pool.connect();
  try {
    const orders = await client.query(
      'SELECT * FROM orders WHERE user_id = $1',
      [userId],
    );
    return orders.rows;
  } finally {
    client.release(); // Always executes
  }
}

Prefer pool.query() for simple queries

// ✅ Best pattern: use pool.query() directly
async function getUserOrders(userId) {
  const orders = await pool.query('SELECT * FROM orders WHERE user_id = $1', [
    userId,
  ]);
  return orders.rows;
}

Detecting missing releases with ESLint

npm install --save-dev eslint-plugin-pg
// .eslintrc.js
import pg from 'eslint-plugin-pg';
export default [pg.configs.recommended];

The rule no-missing-client-release flags any pool.connect() call where client.release() is not guaranteed on all code paths.

src/orders.ts
  3:17  error  🔒 CWE-772 | Missing client.release() detected
               Fix: Add client.release() in finally block or use pool.query() for simple queries

What the rule checks

  • Every pool.connect() call
  • Every possible execution path through the function
  • Whether client.release() is called on all paths
  • Whether it is placed inside a finally block (recommended)

Results after adoption

  • 0 connection leaks in 6 months
  • No more 3 AM pages for connection exhaustion
  • CI now catches issues before they reach staging

Get started

npm install --save-dev eslint-plugin-pg
import pg from 'eslint-plugin-pg';
export default [pg.configs.recommended];

Rule docs: no-missing-client-release
Package: eslint-plugin-pg (npm)

Don’t wait for the next 3 AM wake‑up call.

Back to Blog

Related posts

Read more »