백엔드 면접 용어 사전
면접 태도·시나리오HikariCP

커넥션 풀 고갈

커넥션을 반납하지 못해 대기가 쌓이는 장애. 느린 쿼리·트랜잭션 장기 점유·풀 크기 오설정이 원인이다.

DB 커넥션 풀에 여유 커넥션이 없어 요청이 대기하다 타임아웃하는 상황. 면접 단골 시나리오이자 실제로 매우 자주 발생하는 장애다.

증상

HikariPool-1 - Connection is not available, request timed out after 30000ms

  • 특정 API만이 아니라 DB를 쓰는 전부가 느려진다
  • CPU·메모리는 멀쩡한데 응답이 안 온다
  • 스레드 덤프에 getConnection() 에서 대기 중인 스레드가 잔뜩

왜 커넥션은 풀로 관리하나

커넥션 하나를 새로 만드는 비용 = TCP 3-way handshake + 인증 + 세션 설정 ≈ 수십 ms

  • 요청마다 만들면 그 자체가 병목이다
  • 미리 만들어 두고 빌려 쓴다

원인 — 빈도순

① 커넥션을 오래 쥐고 있다 (가장 흔함)

@Transactional
public void order() {
    repo.save(order);
    externalApi.call();      // ← 3초. 그동안 커넥션을 쥐고 있다
    repo.save(log);
}

트랜잭션이 시작되면 커넥션이 할당되고 커밋까지 반납되지 않는다. 그 안에 외부 API 호출이 있으면 커넥션을 3초씩 낭비한다.

→ 외부 호출을 트랜잭션 밖으로 빼는 것이 정석이다.

② 커넥션 누수

Connection conn = dataSource.getConnection();
// ... 예외 발생
conn.close();   // 실행되지 않는다 → 영영 반납 안 됨

try-with-resources를 쓰면 방지된다. 스프링을 쓰면 대부분 자동이지만, 직접 JDBC를 다루는 코드에서 발생한다.

③ 슬로우 쿼리 쿼리 하나가 10초 걸리면 그 커넥션이 10초간 묶인다.

④ 풀 크기가 실제 부하보다 작다

⑤ REQUIRES_NEW 중첩

  • 바깥 트랜잭션 커넥션 + 안쪽 새 트랜잭션 커넥션 = 요청당 2개
  • 풀 크기 10에서 동시 10개 요청이 오면 전부 두 번째를 기다리며 데드락

⑥ 비동기 처리에서 커넥션을 넘김 @Async 스레드로 엔티티를 넘기면 지연 로딩이 새 커넥션을 요구한다.

진단

// HikariCP 지표 (Actuator/Micrometer)
hikaricp.connections.active     // 사용 중
hikaricp.connections.pending    // 대기 중  ← 0이 아니면 경보
hikaricp.connections.usage      // 사용 시간 분포
jstack <pid> | grep -A 10 "getConnection"   # 어디서 기다리는가
SHOW PROCESSLIST;   -- DB 쪽에서 실제로 무엇이 실행 중인지

pending이 지속적으로 0보다 크면 풀이 부족하거나 반납이 늦는 것이다.

풀 크기 — 크게 잡는 것이 답이 아니다

connections = ((core_count × 2) + effective_spindle_count) HikariCP 문서의 권장식이다. 코어 8개면 약 17개가 나온다.

왜 작게 잡나 — DB도 커넥션마다 메모리와 프로세스/스레드를 쓴다. 커넥션이 많으면 DB 쪽에서 컨텍스트 스위칭과 락 경합이 늘어 오히려 전체 처리량이 떨어진다.

  • 풀을 100으로 늘렸더니 오히려 느려졌다 — ← 실제로 흔한 일

애플리케이션 인스턴스가 여러 대면 합계가 DB의 max_connections를 넘지 않는지도 봐야 한다.

설정

spring.datasource.hikari:
  maximum-pool-size: 20
  minimum-idle: 20            # max와 같게 (풀 크기 변동을 없앰)
  connection-timeout: 3000    # 대기 3초 후 실패 — 빨리 실패시킨다
  validation-timeout: 1000
  max-lifetime: 1800000       # DB의 wait_timeout 보다 짧게 ★
  leak-detection-threshold: 60000   # 60초 이상 안 돌아오면 로그

max-lifetime이 DB의 wait_timeout보다 길면 DB가 먼저 끊은 죽은 커넥션을 풀이 계속 들고 있다가 사용 시점에 실패한다. "간헐적으로 커넥션 오류가 난다"의 흔한 원인이다.

근본 대책

  • ① 트랜잭션을 짧게. 외부 호출을 트랜잭션 밖으로
  • ② 슬로우 쿼리 제거
  • ③ 읽기 전용은 복제본으로 분리 (풀도 분리 = 벌크헤드)
  • ④ connection-timeout 을 짧게 → 전체가 멈추기 전에 빨리 실패
  • ⑤ 지표에 알림을 건다 (pending > 0)

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 면접 시나리오·트러블슈팅 — 종합·STAR