우리 프로덕션 데이터베이스를 무너뜨린 Connection Leak
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 분 안에 풀을 고갈시켰습니다.
흔한 누수 시나리오
| Scenario | Result |
|---|---|
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시 호출을 기다리지 마세요.