이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- HikariCP의 핵심 설정은 무엇이고 어떤 의미인가?
maximumPoolSize는 어떻게 정해야 하는가?- 풀 고갈과 DB 과부하를 구분하려면 어떤 지표를 봐야 하는가?
개요
Spring Boot에서 JDBC DataSource를 쓰면 HikariCP를 기본 커넥션 풀로 사용하는 경우가 많다. HikariCP는 빠르고 단순한 풀을 지향하지만, 설정 의미를 모르고 숫자만 키우면 장애를 키울 수 있다.
커넥션 풀은 성능 장치이면서 제한 장치다. DB로 들어가는 동시 작업 수를 제한해 DB를 보호한다. 풀 대기가 생긴다고 무조건 pool size를 키우는 것은 브레이크가 밟힌 차에서 브레이크를 제거하는 것과 비슷할 수 있다.
원리
HikariCP pool 크기 설계의 원리는 “대기 요청을 없애는 것”이 아니라 DB가 감당 가능한 동시 작업 수를 애플리케이션 앞에서 제한하는 것이다. 풀은 성능 장치이면서 backpressure 장치다. 작으면 정상 트래픽도 기다리고, 크면 장애 순간 DB를 더 세게 밀어붙인다.
따라서 pool size는 애플리케이션 스레드 수가 아니라 DB 처리량, 쿼리 시간, 인스턴스 수, batch/admin workload, timeout 정책을 함께 보고 정한다. maximumPoolSize 하나만 바꾸는 것은 시스템 전체 동시성 예산을 바꾸는 일이다.
핵심 설정
대표 설정은 다음과 같다.
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.connection-timeout=3000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.leak-detection-threshold=2000maximumPoolSize는 풀에서 동시에 사용할 수 있는 최대 커넥션 수다.minimumIdle은 유지할 idle 커넥션 수다.connectionTimeout은 커넥션을 얻기 위해 기다릴 최대 시간이다.idleTimeout은 놀고 있는 커넥션을 정리하는 기준이다.maxLifetime은 커넥션을 너무 오래 재사용하지 않도록 교체하는 기준이다.leakDetectionThreshold는 커넥션을 오래 반납하지 않는 코드를 찾는 데 도움을 준다.
운영 설정은 DB, 네트워크, 클라우드 프록시, 애플리케이션 인스턴스 수에 맞춰야 한다.
HikariCP README도 설정값의 의미를 명확히 구분한다. 특히 connectionTimeout은 pool에서 connection을 기다리는 최대 시간이고, maxLifetime은 DB나 네트워크가 먼저 connection을 끊기 전에 pool이 부드럽게 교체하도록 잡는다. 클라우드 DB, proxy, load balancer의 idle/max lifetime 정책과 어긋나면 간헐적 연결 오류가 생길 수 있다.
Pool 크기 판단
pool size는 “동시 요청 수”와 같지 않다. 모든 요청이 DB를 쓰는 것도 아니고, DB가 모든 동시 쿼리를 효율적으로 처리하는 것도 아니다.
기본 계산은 다음처럼 시작한다.
총 가능 커넥션 = application instance 수 x maximumPoolSizeDB max_connections가 200이고 애플리케이션 인스턴스가 8개라면, 인스턴스당 30개 풀은 이미 240개라 위험하다. 운영 접속, migration, 배치, 모니터링, replica 연결도 여유가 필요하다.
pool size를 늘리기 전에 다음을 확인한다.
- DB CPU가 여유 있는가?
- 느린 쿼리와 락 대기가 없는가?
- connection acquire time만 긴가, query time도 긴가?
- 요청 스레드가 DB 작업 외에 무엇을 기다리는가?
- 인스턴스 수가 늘어날 가능성이 있는가?
지표로 보는 상태
HikariCP 지표는 Micrometer와 Actuator로 수집할 수 있다.
management.endpoints.web.exposure.include=health,metrics,prometheus
management.metrics.enable.hikaricp=true중요 지표는 다음이다.
- active connections가 maximum에 붙어 있는가?
- idle connections가 거의 0인가?
- pending threads가 증가하는가?
- connection acquire time이 증가하는가?
- timeout 예외가 발생하는가?
예외 로그는 다음처럼 보일 수 있다.
java.sql.SQLTransientConnectionException:
HikariPool-1 - Connection is not available, request timed out after 3000ms.이 로그는 “DB가 느리다”가 아니라 “애플리케이션이 제한 시간 안에 커넥션을 얻지 못했다”는 뜻이다. 원인은 느린 쿼리, 락 대기, 커넥션 누수, pool size 부족, DB 장애일 수 있다.
판단은 다음처럼 나눈다.
| 지표 | 해석 출발점 |
|---|---|
| active=max, pending 증가, DB CPU 낮음 | lock wait, 긴 트랜잭션, 외부 I/O, leak 의심 |
| active=max, DB CPU 높음 | slow query, 쿼리 동시성 과다, pool 과대 가능성 |
| idle 많음, timeout 발생 | 네트워크/DB 연결 오류, pool 설정/driver 문제 의심 |
| acquire time 증가, query time 정상 | pool 부족 또는 connection 보유 시간 증가 |
| query time 증가, acquire time 증가 | DB 병목이 pool 고갈로 전파 |
실전 팁
- pool size는 인스턴스 수와 DB 전체 연결 예산을 함께 계산한다.
- connection timeout은 풀 고갈을 빨리 드러내는 보호 장치로 본다.
- pool 지표는 DB 지표와 같은 대시보드에서 본다.
- batch와 API가 같은 pool을 쓰는지 확인한다.
- 설정 변경 후에는 DB CPU, lock wait, acquire time을 함께 관찰한다.
minimumIdle은 특별한 이유가 없으면 HikariCP 기본 동작을 이해한 뒤 정한다. 무작정 maximum과 같게 두면 idle connection이 불필요하게 유지될 수 있다.maxLifetime은 DB/proxy가 connection을 강제로 끊는 시간보다 짧게 잡는 것이 일반적으로 안전하다.- pool 변경은 인스턴스 수와 함께 rollout 중 최대치를 계산한다.
주니어 팁
처음에는 pool size를 크게 잡지 않는다. 작은 pool은 문제를 빨리 드러내고 DB를 보호한다. 개발 환경에서 timeout이 나면 로그를 보고 어떤 요청이 커넥션을 오래 잡는지 찾는다.
JDBC를 직접 쓴다면 반드시 try-with-resources를 사용한다.
try (Connection connection = dataSource.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
// row mapping
}
}Spring의 JdbcTemplate이나 JPA를 쓰면 대부분 프레임워크가 반납을 관리하지만, 트랜잭션 경계가 길면 커넥션 점유도 길어진다.
시니어 팁
시니어는 pool size를 시스템 전체 예산으로 본다.
- API 서버 pool
- batch 서버 pool
- admin 서버 pool
- migration 도구 연결
- read replica 연결
- 운영자 접속 여유
DB가 느려질 때 pool은 보호막이 된다. 모든 서버의 pool을 크게 열어두면 장애 순간 DB에 너무 많은 작업이 몰리고 복구가 더 어려워질 수 있다.
위험 신호!
- connection timeout이 나자마자
maximumPoolSize를 두 배로 늘린다. - DB
max_connections와 애플리케이션 인스턴스 수를 함께 계산하지 않는다. - pool 지표가 수집되지 않는다.
- batch와 API가 같은 DB와 같은 시간대에 경쟁한다.
- connection leak detection 로그를 무시한다.
connectionTimeout을 SQL timeout으로 오해한다.- DB/proxy connection lifetime보다 Hikari
maxLifetime을 길게 둬 간헐적 끊김을 만든다. - pending이 늘었다는 이유만으로 pool을 키우고 DB CPU/lock wait를 보지 않는다.
확인 질문
maximumPoolSize를 정할 때 반드시 함께 봐야 하는 것은 무엇인가?- 애플리케이션 인스턴스 수, DB max connections, DB 처리 능력, 배치와 운영 연결 여유, 쿼리 평균 실행 시간을 함께 봐야 한다.
- connection timeout 예외가 의미하는 것은 무엇인가?
- 제한 시간 안에 풀에서 커넥션을 얻지 못했다는 뜻이며, 원인은 풀 부족뿐 아니라 느린 쿼리와 락 대기일 수 있다.
- pool size를 너무 크게 잡으면 어떤 문제가 생기는가?
- DB로 들어가는 동시 쿼리 수가 늘어 CPU, I/O, 락 경합이 심해지고 장애가 더 크게 번질 수 있다.
- active connection이 maximum에 붙었을 때 바로 pool을 키우면 안 되는 이유는 무엇인가?
- 원인이 slow query, lock wait, 긴 트랜잭션, 외부 I/O라면 pool을 키워 DB로 들어가는 동시 작업만 늘려 장애를 키울 수 있기 때문이다.