HikariCP 커넥션 풀 심화 튜닝

커넥션 풀 사이징이 중요한 이유

HikariCP는 Spring Boot의 기본 커넥션 풀이지만, 기본 설정(maximumPoolSize=10)을 그대로 사용하면 동시 요청이 몰릴 때 커넥션 고갈이 발생합니다. 반대로 풀을 너무 크게 잡으면 DB 서버의 연결 한도를 초과하거나 컨텍스트 스위칭 오버헤드가 증가합니다.

Spring Boot HikariCP 커넥션 풀에서 기본 설정을 다뤘다면, 이 글에서는 운영 환경 튜닝, 모니터링, 장애 대응에 집중합니다.

최적 풀 사이즈 공식

HikariCP 공식 문서가 권장하는 풀 사이즈 공식입니다:

connections = (core_count * 2) + effective_spindle_count

SSD 환경(spindle=0)에서 4코어 서버라면 약 8~10개가 적정입니다. 직관과 달리 풀 사이즈를 늘린다고 성능이 올라가지 않습니다.

서버 스펙 권장 풀 사이즈 비고
2코어 SSD 5~6 소규모 서비스
4코어 SSD 8~10 일반적인 운영 환경
8코어 SSD 15~20 대규모 트래픽
다중 인스턴스 (4개) 인스턴스당 5~8 DB max_connections / 인스턴스 수

핵심 원칙: DB의 max_connections를 전체 애플리케이션 인스턴스의 풀 사이즈 합으로 나눠야 합니다. MySQL 기본 151개에서 시스템 예약 20개를 빼면 131개. 인스턴스 4개면 인스턴스당 최대 32개입니다.

운영 환경 설정

# application.yml
spring:
  datasource:
    hikari:
      # 풀 사이즈
      maximum-pool-size: 10
      minimum-idle: 5              # idle 커넥션 최소 유지 수
      
      # 타임아웃
      connection-timeout: 3000     # 커넥션 획득 대기 (3초, 기본 30초는 너무 김)
      idle-timeout: 600000         # 유휴 커넥션 제거 (10분)
      max-lifetime: 1800000        # 커넥션 최대 수명 (30분)
      validation-timeout: 3000     # 유효성 검증 타임아웃
      
      # 커넥션 검증
      connection-test-query: SELECT 1
      
      # 누수 감지
      leak-detection-threshold: 10000  # 10초 이상 반환 안 하면 경고
      
      # 풀 이름 (모니터링용)
      pool-name: MainHikariPool
      
      # 메트릭
      register-mbeans: true

주요 설정 해설

설정 기본값 권장 이유
connection-timeout 30000ms 3000ms 빠른 실패가 사용자 경험에 유리
max-lifetime 1800000ms 1800000ms DB wait_timeout보다 짧아야 함
minimum-idle = max max의 50% 유휴 시 리소스 절약
leak-detection-threshold 0(비활성) 10000ms 커넥션 누수 조기 발견

커넥션 누수 감지와 디버깅

leak-detection-threshold를 설정하면 HikariCP가 커넥션을 빌려간 후 지정 시간 내 반환하지 않으면 스택트레이스를 로그에 출력합니다.

// ⚠️ 커넥션 누수 예시: try-with-resources 미사용
public List<Order> findOrders() {
    Connection conn = dataSource.getConnection();  // 빌려감
    // ... 쿼리 실행
    // conn.close() 호출 안 함 → 누수!
    return orders;
}

// ✅ 올바른 사용
public List<Order> findOrders() {
    try (Connection conn = dataSource.getConnection()) {
        // ... 쿼리 실행
        return orders;
    }  // 자동 반환
}

JPA/Spring Data를 사용하면 대부분 자동 관리되지만, @Transactional 범위가 넓거나 외부 API 호출을 트랜잭션 안에서 하면 사실상 누수와 같은 효과가 발생합니다.

// ⚠️ 안티패턴: 트랜잭션 안에서 외부 API 호출
@Transactional
public void processOrder(Long orderId) {
    Order order = orderRepository.findById(orderId).orElseThrow();
    
    // 외부 결제 API 호출 (3~5초 소요) → 커넥션 점유 중!
    PaymentResult result = paymentClient.charge(order.getAmount());
    
    order.complete(result.getTransactionId());
    orderRepository.save(order);
}

// ✅ 개선: 외부 호출은 트랜잭션 밖에서
public void processOrder(Long orderId) {
    Order order = orderRepository.findById(orderId).orElseThrow();
    
    // 트랜잭션 밖에서 외부 호출
    PaymentResult result = paymentClient.charge(order.getAmount());
    
    // 짧은 트랜잭션으로 DB 작업만
    orderService.completeOrder(orderId, result.getTransactionId());
}

Micrometer 메트릭 모니터링

Spring Micrometer 커스텀 메트릭과 연동하면 HikariCP 상태를 실시간 모니터링할 수 있습니다.

# application.yml
management:
  metrics:
    enable:
      hikaricp: true
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus

자동 등록되는 주요 메트릭:

메트릭 의미 알람 기준
hikaricp_connections_active 사용 중인 커넥션 > max의 80%
hikaricp_connections_pending 대기 중인 스레드 > 0이 지속되면 위험
hikaricp_connections_idle 유휴 커넥션 0이면 풀 부족
hikaricp_connections_timeout_total 타임아웃 발생 횟수 > 0이면 즉시 조사
hikaricp_connections_creation_seconds 커넥션 생성 시간 > 1초면 네트워크 점검
# Prometheus 알람 룰 예시
groups:
  - name: hikaricp
    rules:
      - alert: HikariPoolExhausted
        expr: hikaricp_connections_pending{pool="MainHikariPool"} > 0
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "커넥션 풀 대기열 발생"
      
      - alert: HikariConnectionTimeout
        expr: rate(hikaricp_connections_timeout_total[5m]) > 0
        labels:
          severity: critical
        annotations:
          summary: "커넥션 획득 타임아웃 발생"

다중 DataSource 풀 분리

읽기/쓰기 분리나 배치 전용 풀을 구성할 때 DataSource를 분리합니다.

@Configuration
public class DataSourceConfig {

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.hikari")
    public HikariDataSource primaryDataSource() {
        return DataSourceBuilder.create()
            .type(HikariDataSource.class)
            .build();
    }

    @Bean("batchDataSource")
    public HikariDataSource batchDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://replica:3306/mydb");
        config.setUsername("batch_user");
        config.setPassword("password");
        config.setPoolName("BatchHikariPool");
        config.setMaximumPoolSize(3);           // 배치는 적은 커넥션
        config.setConnectionTimeout(10000);      // 배치는 대기 허용
        config.setMaxLifetime(3600000);          // 1시간
        return new HikariDataSource(config);
    }
}

정리

HikariCP 튜닝의 핵심은 풀 사이즈를 작게 유지하고(cores×2), connection-timeout을 짧게(3초) 설정하여 빠르게 실패하는 것입니다. leak-detection-threshold로 누수를 조기 발견하고, Micrometer 메트릭의 pending/timeout을 모니터링하면 커넥션 풀 관련 장애를 예방할 수 있습니다. 트랜잭션 범위를 최소화하여 커넥션 점유 시간을 줄이는 것이 가장 효과적인 최적화입니다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux