DB 커넥션 풀 고갈 장애 대응 플레이북 - HikariCP timeout부터 커넥션 누수 추적까지
서비스가 갑자기 DB에 연결하지 못할 때 어떤 순서로 원인을 추적하는지, HikariCP 지표에서 무엇을 봐야 하는지, 누수인지 과부하인지 어떻게 구분하는지 장애 대응 관점에서 정리합니다.
커넥션 풀 고갈은 연쇄 장애를 만든다
DB 커넥션 풀이 바닥나면 애플리케이션이 DB에 붙을 수 없습니다.
겉으로 드러나는 증상은 다양합니다.
- API 응답이 느려지다가 503이 나기 시작한다
- 로그에
HikariPool-1 - Connection is not available, request timed out after 30000ms가 쌓인다 - 헬스체크가 실패해서 Pod가 재시작된다
- 재시작된 Pod가 다시 같은 이유로 죽는다
Pod 재시작이 반복되면 커넥션 문제인지 다른 원인인지 헷갈리기 쉽습니다.
가장 먼저 확인할 것
1. 실제 커넥션 풀 상태
HikariCP 지표를 보는 것이 가장 빠릅니다.
Prometheus + Grafana가 있으면 아래 지표를 봅니다.
hikaricp_connections_active 현재 사용 중인 커넥션 수
hikaricp_connections_idle 사용 가능한 커넥션 수
hikaricp_connections_pending 대기 중인 요청 수
hikaricp_connections_timeout_total 타임아웃 발생 횟수 (누적)
active가 최대값에 붙어 있고 idle이 0이면 풀이 고갈된 상태입니다.
pending이 올라가면 요청이 커넥션을 기다리고 있는 것입니다.
빠른 확인이 필요하면 Spring Actuator 엔드포인트를 직접 봅니다.
curl http://localhost:8080/actuator/metrics/hikaricp.connections.active
curl http://localhost:8080/actuator/metrics/hikaricp.connections.idle
2. 풀 사이즈 설정
# application.yaml
spring:
datasource:
hikari:
maximum-pool-size: 10 # 최대 커넥션 수
minimum-idle: 5 # 최소 유지 커넥션 수
connection-timeout: 30000 # 커넥션 대기 타임아웃 (ms)
idle-timeout: 600000 # 미사용 커넥션 반환 시간 (ms)
max-lifetime: 1800000 # 커넥션 최대 수명 (ms)
maximum-pool-size가 너무 작게 설정됐거나,
트래픽이 급증했는데 사이즈가 그대로인 경우가 많습니다.
원인 구분: 누수인가, 과부하인가
원인에 따라 대응 방향이 다릅니다.
커넥션 누수 (Leak)
커넥션을 가져갔다가 반환하지 않는 경우입니다.
신호:
- 트래픽이 줄어도
active커넥션이 줄지 않는다 - 서비스를 재시작하면 잠깐 정상이다가 다시 고갈된다
- 시간이 지날수록
active가 서서히 올라간다
주요 원인:
// 나쁜 패턴 - finally 없이 커넥션을 닫지 않는 경우
Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
// 예외 발생 시 conn이 닫히지 않음
// 올바른 패턴
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
// 자동으로 닫힘
}
HikariCP의 누수 감지를 활성화하면 어디서 누수가 생기는지 로그로 확인할 수 있습니다.
spring:
datasource:
hikari:
leak-detection-threshold: 60000 # 60초 이상 반환 안 되면 경고
과부하 (Overload)
커넥션을 정상적으로 반환하지만, 요청 속도가 풀 처리 속도를 초과하는 경우입니다.
신호:
- 트래픽 급증과 동시에
pending이 올라간다 - 응답 시간이 일정 트래픽 임계값부터 급격히 늘어난다
- 재시작 직후에도 증상이 바로 재현된다
주요 원인:
- 쿼리가 느려져서 커넥션 점유 시간이 길어졌다
- 외부 API 대기가 DB 트랜잭션 안에 있다
- N+1 쿼리로 한 요청이 커넥션을 여러 번 쓴다
즉시 완화 조치
1. 풀 사이즈 임시 증가
spring:
datasource:
hikari:
maximum-pool-size: 20 # 기존 10 → 20으로 증가
무한정 늘리면 DB 서버 쪽 커넥션 한계에 걸립니다.
DB가 허용하는 max_connections 안에서 조정해야 합니다.
-- PostgreSQL 현재 커넥션 수 확인
SELECT count(*) FROM pg_stat_activity;
SHOW max_connections;
2. 느린 쿼리 즉시 확인
-- PostgreSQL 현재 실행 중인 느린 쿼리
SELECT pid, now() - pg_stat_activity.query_start AS duration, query
FROM pg_stat_activity
WHERE state = 'active'
ORDER BY duration DESC
LIMIT 10;
장시간 실행 중인 쿼리가 커넥션을 붙잡고 있을 수 있습니다.
-- 필요하면 강제 종료
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
WHERE duration > interval '5 minutes';
재발 방지
트랜잭션 범위 점검
트랜잭션이 필요 이상으로 길면 커넥션을 오래 점유합니다.
// 나쁜 패턴 - 외부 API 호출이 트랜잭션 안에 있음
@Transactional
public void processOrder(Order order) {
orderRepository.save(order);
externalPaymentApi.charge(order); // 네트워크 지연 = 커넥션 점유 연장
orderRepository.updateStatus(order.getId(), PAID);
}
// 좋은 패턴 - 외부 호출을 트랜잭션 밖으로
public void processOrder(Order order) {
saveOrder(order); // 트랜잭션 1: DB 저장
externalPaymentApi.charge(order); // 트랜잭션 밖: 외부 호출
updateOrderStatus(order.getId()); // 트랜잭션 2: 상태 업데이트
}
알림 설정
hikaricp_connections_pending > 0 → 즉시 알림
hikaricp_connections_timeout_total 증가율 > 0.1/s → 경고
정리
1. HikariCP 지표 확인 (active / idle / pending)
2. 누수인지 과부하인지 구분
- 누수: leak-detection-threshold 활성화, try-with-resources 점검
- 과부하: 느린 쿼리 확인, 트랜잭션 범위 점검
3. 즉시 완화: 풀 사이즈 임시 증가, 느린 쿼리 종료
4. 재발 방지: 트랜잭션 범위 최소화, 모니터링 알림 설정
커넥션 풀 고갈은 대부분 조용하게 시작합니다.
지표를 평소에 보는 습관이 있어야 이상 징후를 빨리 잡을 수 있습니다.