우리 프로덕션 데이터베이스를 무너뜨린 Connection Leak

발행: (2026년 1월 1일 오전 06:35 GMT+9)
3 min read
원문: 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.

코드베이스에 숨어 있던 연결 누수

// ❌ 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()? 🤔
}

모든 호출이 연결을 누수했습니다. 분당 50 요청이면 2 분 안에 풀을 고갈시켰습니다.

흔한 누수 시나리오

ScenarioResult
release() 를 완전히 잊음연결이 반환되지 않음
release() 이전에 조기 반환연결 누수
예외 발생finally 블록 누락
비동기 오류처리되지 않은 거부, 정리 없음

누수 해결하기

finally 블록에서 항상 release 하기

// ✅ 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
  }
}

간단한 쿼리는 pool.query() 사용 권장

// ✅ 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;
}

ESLint 로 누락된 release 감지하기

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

no-missing-client-release 규칙은 client.release() 가 모든 코드 경로에서 보장되지 않을 때 pool.connect() 호출을 표시합니다.

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

규칙이 확인하는 내용

  • 모든 pool.connect() 호출
  • 함수 내 모든 가능한 실행 경로
  • 모든 경로에서 client.release() 가 호출되는지 여부
  • finally 블록 안에 배치되었는지 여부 (권장)

도입 후 결과

  • 6개월 동안 0개의 연결 누수
  • 연결 고갈로 인한 새벽 3시 페이지 알림 종료
  • CI가 스테이징에 도달하기 전에 문제를 잡아냄

시작하기

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)

다음 새벽 3시 호출을 기다리지 마세요.

Back to Blog

관련 글

더 보기 »