데이터베이스 연결 풀링과 커넥션 누수 방지 핵심
애플리케이션 성능과 안정성을 위한 데이터베이스 연결 풀링 설계와 커넥션 누수 방지 원칙
목차
개요
데이터베이스 연결 풀링은 애플리케이션과 DB 사이의 연결을 재활용해 성능을 높이고 자원 사용을 줄이는 기법이다. 잘못된 설계는 커넥션 누수로 이어져 서비스 장애를 초래한다. 이 글은 처음 접하는 개발자도 이해할 수 있도록 풀링의 동작 원리, 설정 포인트, 그리고 실무에서 흔히 발생하는 누수 원인과 방지 방법을 설명한다.
풀링의 기본 개념
왜 풀링이 필요한가
새로운 DB 연결은 비용이 크다. 연결 생성과 인증, 핸드셰이크 과정이 반복되면 응답 지연이 발생한다. 풀링은 미리 만들어 둔 연결을 재사용해 연결 지연과 자원 낭비를 줄인다.
풀의 핵심 파라미터
- 최대 연결 수(max): 동시에 허용할 연결 수
- 최소 유휴 연결(min idle): 풀에 유지할 최소 유휴 연결 수
- 최대 유휴 시간(max idle time): 사용되지 않는 연결을 닫는 시간
- 커넥션 타임아웃: 클라이언트 요청 대기 시간
Node DB 커넥션 풀링 설정 예시
Node.js에서는 mysql2, pg 등 클라이언트 라이브러리가 풀 기능을 제공한다. 아래는 mysql2와 pg의 간단한 설정 예시다.
mysql2 예시
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'db-host',
user: 'db-user',
password: 'db-pass',
database: 'mydb',
waitForConnections: true,
connectionLimit: 20, // max
queueLimit: 0
});
async function query(sql, params) {
const [rows] = await pool.execute(sql, params);
return rows;
}
pg (Postgres) 예시
const { Pool } = require('pg');
const pool = new Pool({
host: 'db-host',
user: 'db-user',
password: 'db-pass',
database: 'mydb',
max: 20, // max
idleTimeoutMillis: 30000, // max idle
connectionTimeoutMillis: 2000
});
async function getClient() {
const client = await pool.connect();
try {
// 쿼리 실행
} finally {
client.release();
}
}
pool max idle 설정 Node
pool의 idle 관련 설정은 유휴 연결을 얼마나 오래 유지할지 결정한다. 너무 낮게 설정하면 연결을 자주 재생성해 오버헤드가 커진다. 너무 높게 설정하면 불필요한 자원이 점유된다. 트래픽 패턴을 기준으로 적절한 idleTimeoutMillis 또는 maxIdle 설정을 선택한다.
설정 전략
- 피크 트래픽과 평균 트래픽을 분석해 max를 결정
- 유휴 시간은 짧은 배치성 작업이 빈번하면 짧게, 일정한 트래픽이면 길게 설정
- 모니터링 지표(CPU, 메모리, DB active connections)를 기준으로 조정
커넥션 누수 원인과 증상
주요 원인
- try/finally 없이 클라이언트 반환을 놓침
- 비동기 흐름에서 예외 발생 후 release 미실행
- 긴 쿼리나 트랜잭션이 커넥션을 장시간 점유
- 풀 설정 부적절로 인해 대기열 증가
관찰 가능한 증상
- DB active connections가 지속적으로 증가
- 새 연결 생성 실패 또는 timeout 에러 발생
- 응답 지연과 오류율 상승
커넥션 누수 방지 Node.js
커넥션 누수 방지 Node.js 측면에서는 코드 패턴과 런타임 설정이 핵심이다. 항상 연결을 반환하는 패턴을 적용하고, 예외에 대비해 안전한 클린업 로직을 둔다.
안전한 사용 패턴
- pool.connect() 사용 시 try/finally로 release 보장
- 짧고 원자적인 쿼리 단위 유지
- 가능하면 트랜잭션 범위를 좁게 유지
- 타임아웃으로 장시간 점유 방지
// 안전한 클라이언트 사용 예시
async function safeTransaction(pool) {
const client = await pool.connect();
try {
await client.query('BEGIN');
// 작업
await client.query('COMMIT');
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
}
모니터링과 경보
누수를 조기에 발견하려면 모니터링이 필수다. 모니터링 항목은 다음과 같다.
- 풀의 active / idle connection 수
- 대기 큐 길이와 평균 대기 시간
- DB 측의 세션 수와 쿼리 지연
- 애플리케이션의 에러율 및 타임아웃 빈도
이 지표를 기반으로 임계값을 정해 경보를 설정하면 문제 발생 시 빠른 대응이 가능하다.
디버깅 체크리스트
- 코드에서 모든 분기에서 release가 실행되는지 확인
- 비동기 예외 경로에 대한 테스트 작성
- 장기 실행 쿼리 로그와 인덱스 상태 점검
- 풀 설정 변경 후 부하 테스트로 동작 확인
결론
연결 풀링은 효율성과 안정성 향상에 필수적이다. 적절한 pool max, idle 설정과 안전한 코드 패턴이 조합되면 커넥션 누수를 효과적으로 방지할 수 있다. 모니터링과 경보 체계가 뒷받침되면 문제를 조기에 감지해 서비스 가용성을 지킬 수 있다.