HikariCP

JDBC 커넥션 풀 라이브러리로, 바이트코드 최적화와 경량 설계로 가장 빠른 성능을 제공하며 Spring Boot 2.0+의 기본 커넥션 풀입니다.

왜 HikariCP인가?

"Hikari"는 일본어로 빛(光)이라는 뜻으로, 빛처럼 빠르다는 의미를 담고 있습니다.

벤치마크 (커넥션 획득/반납, ops/ms)

HikariCP   ████████████████████████████████████  ~35,000
Tomcat     ████████████                          ~12,000
DBCP2      ██████████                            ~10,000
C3P0       ████                                  ~ 4,000

HikariCP가 빠른 이유

1. FastList

기본 ArrayList 대신 자체 구현한 FastList를 사용합니다.

// ArrayList.get() — 매번 범위 체크
public E get(int index) {
    rangeCheck(index);       // if (index >= size) throw ...
    return elementData[index];
}

// FastList.get() — 범위 체크 생략
public T get(int index) {
    return elementData[index]; // 바로 접근, 미세한 차이가 누적됨
}

2. ConcurrentBag

커넥션 관리에 락(Lock) 경합을 최소화하는 자체 자료구조를 사용합니다.

[ ConcurrentBag 동작 ]

스레드 A가 커넥션 요청:
  1단계: ThreadLocal에서 이전에 사용한 커넥션 검색 (락 없음, 가장 빠름)
  2단계: 없으면 → 공유 리스트(sharedList)에서 CAS 연산으로 획득
  3단계: 없으면 → handoffQueue에서 대기 (다른 스레드가 반납할 때까지)
단계방식락 경합
ThreadLocal 탐색자기 스레드 전용없음
sharedList CASCompare-And-Swap최소
handoffQueue 대기블로킹있음 (최후 수단)

3. 프록시 최적화

JDBC Connection, Statement, ResultSet의 프록시를 바이트코드 수준에서 최적화하여 JDK Proxy나 CGLIB보다 오버헤드가 적습니다.

[일반 커넥션 풀]
conn.prepareStatement() → 프록시 인터셉트 (리플렉션) → 실제 호출
                          (느림)

[HikariCP]
conn.prepareStatement() → 프록시 인터셉트 (바이트코드 직접 호출) → 실제 호출
                          (빠름)

주요 설정

spring:
  datasource:
    hikari:
      # === 풀 크기 ===
      maximum-pool-size: 10      # 최대 커넥션 수
      minimum-idle: 10           # 최소 유휴 커넥션 (공식: max와 동일 권장)

      # === 타임아웃 ===
      connection-timeout: 3000   # 커넥션 획득 대기 (ms, 기본 30초)
      idle-timeout: 600000       # 유휴 커넥션 제거 시간 (ms, 기본 10분)
      max-lifetime: 1800000      # 커넥션 최대 수명 (ms, 기본 30분)
      validation-timeout: 5000   # 커넥션 유효성 검사 타임아웃 (ms)

      # === 커넥션 검증 ===
      connection-test-query: SELECT 1   # JDBC4 미지원 드라이버용
      # JDBC4 드라이버는 Connection.isValid() 자동 사용 (설정 불필요)

      # === 누수 감지 ===
      leak-detection-threshold: 2000    # 반납까지 2초 초과 시 경고 로그

핵심 설정 상세

maximum-pool-size

pool size = (core_count × 2) + effective_spindle_count

예: 4코어 서버, SSD 1개
→ (4 × 2) + 1 = 9 ~ 10

HikariCP 공식 문서에서는 대부분의 경우 10개면 충분하다고 강조합니다.

max-lifetime

DB의 wait_timeout보다 2~3초 짧게 설정해야 합니다.

MySQL wait_timeout = 28800초 (8시간)
→ max-lifetime = 1800000ms (30분) 정도가 안전

이유: DB가 먼저 커넥션을 끊으면 애플리케이션은 죽은 커넥션을 사용하게 됨

minimum-idle

[minimum-idle < maximum-pool-size 인 경우]
트래픽 증가 → 커넥션 새로 생성 → TCP handshake + 인증 → 지연 발생

[minimum-idle = maximum-pool-size (권장)]
항상 고정된 수의 커넥션 유지 → 생성/제거 오버헤드 없음

모니터링

Micrometer + Actuator 연동

management:
  endpoints:
    web:
      exposure:
        include: health, metrics
// 주요 메트릭
GET /actuator/metrics/hikaricp.connections.active    // 사용 중 커넥션
GET /actuator/metrics/hikaricp.connections.idle      // 유휴 커넥션
GET /actuator/metrics/hikaricp.connections.pending   // 대기 중 요청 ← 이 값이 높으면 위험
GET /actuator/metrics/hikaricp.connections.timeout   // 타임아웃 발생 횟수

주의해야 할 지표

메트릭정상위험 신호
active< max-pool-size지속적으로 max에 근접
pending0~1지속적으로 > 0
timeout0증가 추세

커넥션 누수 방지

// X 잘못된 사용: 예외 발생 시 커넥션 미반납 → 누수
Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT ...");
ResultSet rs = ps.executeQuery();
conn.close();  // 예외 발생 시 이 줄에 도달하지 못함

// O 올바른 사용: try-with-resources로 반납 보장
try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement("SELECT ...");
     ResultSet rs = ps.executeQuery()) {
    while (rs.next()) {
        // 처리 로직
    }
}  // 예외 발생 여부와 관계없이 자동 반납

커넥션 풀 라이브러리 비교

항목HikariCPDBCP2Tomcat PoolC3P0
성능
Spring Boot 기본O (2.0+)XXX
코드 크기~130KB~200KB~150KB~600KB
커넥션 검증isValid()쿼리 기반쿼리 기반쿼리 기반
락 전략CAS + ThreadLocalsynchronizedFair/Unfair Locksynchronized
유지보수활발보통보통거의 없음

실무 팁: HikariCP는 Spring Boot 기본이므로 별도 의존성 추가 없이 바로 사용 가능합니다. 성능 이슈 대부분은 풀 사이즈를 과도하게 크게 잡거나, 커넥션 누수에서 비롯되므로 적절한 풀 사이즈 + leak-detection-threshold 설정 + 모니터링이 핵심입니다.

CSLOG에서 보기