본문 바로가기

프로젝트/Techfork

[26/02/23] 오늘의 개발일지 - HikariCP 커넥션 풀 설정

오늘은 application.yml에 HikariCP 커넥션 풀 설정을 추가했습니다.

Spring Boot를 쓰면 HikariCP가 기본 커넥션 풀로 들어오는데, 지금까지 기본값 그대로 쓰고 있었습니다. 부하 테스트 진행 전 커넥션 관련 설정을 명시적으로 잡아둘 필요가 있다고 판단했고, 각 설정값의 근거를 정리해봅니다.

 

추가한 설정

spring:
  datasource:
    hikari:
      # 풀 크기
      maximum-pool-size: 10
      minimum-idle: 10
      # 타임아웃
      connection-timeout: 5000
      idle-timeout: 600000
      max-lifetime: 1800000
      # 커넥션 유효성
      keepalive-time: 60000
      # 모니터링
      pool-name: TechFork-HikariCP

 

1. 풀 크기: maximum-pool-size와 minimum-idle을 같은 값으로

maximum-pool-size: 10
minimum-idle: 10

HikariCP 공식 Wiki에서 권장하는 방식은 고정 크기 풀(fixed-size pool)입니다. minimum-idlemaximum-pool-size를 동일하게 설정하면 됩니다.

 

처음에는 minimum-idle을 5 정도로 낮게 잡아서 리소스를 절약하려 했는데, 이렇게 하면 트래픽이 갑자기 몰릴 때 커넥션을 새로 생성하는 시간이 그대로 응답 지연으로 이어집니다. 커넥션 10개가 차지하는 메모리는 미미한 수준이므로, 미리 만들어두는 게 맞다고 판단했습니다.

 

풀 크기 10은 HikariCP 기본값이기도 한데, 공식 Wiki에서 제시하는 공식이 CPU 코어 수 × 2 + 디스크 스핀들 수입니다. 4코어 서버 기준 4 × 2 + 1 = 9~10이므로 적정 수준입니다.

직관적으로는 커넥션을 많이 만들수록 좋을 것 같지만, 실제로는 DB 측의 컨텍스트 스위칭 비용이 증가해서 오히려 성능이 떨어질 수 있습니다. HikariCP Wiki의 "About Pool Sizing" 문서에서 이 부분을 상세히 다루고 있습니다.

 

 

2. 타임아웃 설정

connection-timeout: 5000 (5초)

풀에서 커넥션을 얻기 위해 대기하는 최대 시간입니다. 기본값은 30초인데, 이건 웹 애플리케이션에서는 너무 깁니다.

사용자가 5초 이상 응답을 기다리고 있다면 그건 이미 장애 상황입니다. 이때 30초까지 스레드를 붙잡고 있으면 다른 요청까지 밀리면서 장애가 확산됩니다. 빠르게 SQLTransientConnectionException을 터뜨리고 에러 핸들링으로 넘기는 게 더 나은 전략입니다.

idle-timeout: 600000 (10분)

유휴 커넥션이 풀에서 제거되기까지의 시간입니다. 다만 위에서 minimum-idle == maximum-pool-size로 고정 크기 풀을 만들었으므로, 이 설정은 사실상 동작하지 않습니다. 유휴 커넥션을 제거할 일이 없기 때문이죠.

그래도 명시적으로 넣어둔 이유는, 나중에 minimum-idle을 낮출 경우를 대비한 방어적 설정입니다.

max-lifetime: 1800000 (30분)

커넥션의 최대 생존 시간입니다. 이 시간이 지나면 사용 중이지 않은 커넥션은 풀에서 제거되고, 사용 중인 커넥션은 반환 시점에 제거됩니다.

이 값에서 중요한 건 MySQL의 wait_timeout보다 반드시 짧아야 한다는 점입니다.

MySQL wait_timeout (기본 8시간) >> max-lifetime (30분)  ✅ 안전

DB가 커넥션을 먼저 끊어버리면 애플리케이션에서 "죽은 커넥션"을 잡게 되어 Communication link failure 에러가 발생합니다. HikariCP 공식 문서에서는 DB나 인프라의 타임아웃보다 최소 30초 이상 짧게 설정하라고 권장합니다.

30분이 짧은 거 아닌가 고민했었는데, 계산해보면 걱정할 수준이 아닙니다.

풀 사이즈 10개, max-lifetime 30분
→ 평균 3분에 1개씩 순차적으로 교체
→ 커넥션 생성 비용: ~1-5ms
→ 전체 처리량에 영향 없음

HikariCP는 교체를 한꺼번에 하지 않고 분산시키기 때문에, 순간적으로 풀이 비는 일도 없습니다. 오히려 주기적으로 교체해주면 DB 세션의 메모리 누수나 네트워크 상태 변화 같은 문제를 예방할 수 있습니다.

 

3. 커넥션 유효성: keepalive-time

keepalive-time: 60000  # 1분

유휴 커넥션에 대해 1분마다 유효성 검사를 수행합니다. JDBC4의 isValid() 또는 SELECT 1 같은 가벼운 쿼리로 커넥션이 살아있는지 확인하고, 죽은 커넥션은 새 것으로 교체합니다.

이 설정이 특히 필요했던 이유는 클라우드 환경 때문입니다. TechFork는 현재 Oracle Cloud에서 운영 중인데, 클라우드의 NAT Gateway나 로드밸런서는 일정 시간(보통 350초) 동안 패킷이 없는 TCP 커넥션을 자동으로 끊어버립니다.

keepalive-time(60초) < 클라우드 idle timeout(~350초)  →  커넥션 유지 ✅

1분마다 핑을 보내주면 클라우드 인프라가 "이 커넥션 아직 쓰고 있구나"라고 인식해서 끊지 않습니다.

 

4. 모니터링: pool-name

pool-name: TechFork-HikariCP

커넥션 풀에 이름을 부여하면 JMX와 로그에서 이 이름으로 식별할 수 있습니다.

TechFork-HikariCP - Pool stats (total=10, active=3, idle=7, waiting=0)

나중에 데이터소스가 추가되면 이름으로 구분할 수 있어서, 미리 넣어두는 게 좋습니다.

 

함께 확인한 것: open-in-view: false

HikariCP 설정을 넣으면서 기존에 설정해두었던 open-in-view: false도 다시 한번 확인했습니다.

spring:
  jpa:
    open-in-view: false

open-in-viewtrue(기본값)면 HTTP 요청의 시작부터 응답 완료까지 DB 커넥션을 점유합니다. 뷰 렌더링이나 JSON 직렬화 시간까지 커넥션을 붙잡고 있는 셈이죠. 아무리 HikariCP를 잘 튜닝해도 이 설정이 true면 커넥션이 금방 고갈됩니다.

false로 해두면 트랜잭션이 끝나는 시점에 커넥션이 반환되므로, 같은 풀 사이즈로도 훨씬 많은 요청을 처리할 수 있습니다.

 

Nginx 타임아웃과의 관계

설정하고 나서 "Nginx의 proxy_read_timeout(60초)과 충돌하는 건 없나?" 확인해봤습니다. 결론은 문제없습니다.

이 둘은 전혀 다른 계층에서 다른 것을 기다리는 설정입니다.

클라이언트 → Nginx (60초 대기) → Spring Boot → HikariCP (5초 대기) → MySQL
                                      │
                                      ├── 커넥션 획득: ~5초 이내
                                      ├── 쿼리 실행: 수 ms ~ 수 초
                                      ├── 비즈니스 로직: 수 ms ~ 수 초
                                      └── 전체 응답: 60초 이내

connection-timeout(5초)은 풀에서 커넥션을 빌려오는 대기 시간이고, proxy_read_timeout(60초)은 Nginx가 Spring Boot의 전체 응답을 기다리는 시간입니다. max-lifetime(30분)은 커넥션 객체 자체의 수명이므로 단일 요청의 타임아웃과는 무관합니다.

각 계층의 타임아웃은 바깥쪽이 안쪽보다 넉넉한 구조를 유지하면 됩니다.

Cloudflare(100초) > Nginx(60초) > Spring Boot 전체 처리 > HikariCP(5초)

 

정리

오늘 추가한 설정의 핵심 원칙을 정리하면 다음과 같습니다.

  • 풀 크기는 고정으로minimum-idle == maximum-pool-size로 트래픽 급증 시 커넥션 생성 지연을 방지
  • 타임아웃은 짧게connection-timeout 5초로 빠른 실패 유도, 장애 확산 방지
  • max-lifetime은 DB보다 짧게 — MySQL wait_timeout(8시간)보다 훨씬 짧은 30분으로 죽은 커넥션 방지
  • keepalive로 이중 안전망 — 클라우드 환경의 idle timeout에 대비하여 1분마다 유효성 검사
  • open-in-view: false — 커넥션 점유 시간 최소화로 풀 효율 극대화

 

참고자료