minimumIdle=maximumPoolSize로 connection을 미리 보관하면 되는데, idle connection을 줄이는 이유가 궁금했습니다. Spring Boot 3.4.4, HikariCP 5.1.0과 로컬 MySQL에서 유지하는 자원과 축소 후 첫 부하의 생성 대기를 비교했습니다.
maximumPoolSize와 minimumIdle의 역할 구분하기
pool 상태는 다음 용어로 구분합니다.
- idle connection: application에 대여되지 않아 다음 요청에 쓸 수 있는 connection입니다.
- active connection: application이 빌려 아직 반환하지 않은 connection입니다. SQL 실행 중인 상태만 뜻하지는 않습니다.
- total connection 수: pool이 관리하는 전체 physical connection 수입니다.
- pending thread:
getConnection()에서 connection을 기다리는 thread입니다.
HikariCP 5.1.0의 주요 설정은 다음과 같습니다. Spring Boot에서는 spring.datasource.hikari.* 아래에 maximum-pool-size, minimum-idle처럼 설정합니다. Spring Boot DataSource 설정, HikariCP 설정
| 속성 | 기본값 | 역할 |
|---|---|---|
maximumPoolSize | 10 | active connection과 idle connection을 합한 pool 크기의 상한 |
minimumIdle | maximumPoolSize와 같음 | pool이 확보하려는 idle connection 수의 목표 최솟값 |
idleTimeout | 600,000ms, 10분 | minimumIdle을 초과한 idle connection의 정리 기준 시간 |
connectionTimeout | 30,000ms, 30초 | application이 pool에서 connection을 얻기까지 기다릴 시간 |
minimumIdle을 생략하면 상한과 같은 fixed-size pool이 됩니다. 이 값은 보충 목표이므로, 상한까지 모두 active connection이면 idle connection은 0개가 될 수 있습니다.
minimumIdle=0도 매 요청마다 connection을 끊는 설정은 아닙니다. 반환한 connection은 재사용하며, minimumIdle < maximumPoolSize일 때 idleTimeout을 넘긴 초과 idle connection을 정리합니다. 보충과 정리 구현
idle connection을 줄여 회수하는 자원 확인하기
application의 Connection.close()는 connection을 pool에 반환합니다. 이후 초과 idle connection의 physical connection까지 닫혀야 DB session과 socket FD(file descriptor)가 해제됩니다. idle connection은 SQL을 실행하지 않아도 session 상태와 buffer, thread 자원, JDBC 객체를 유지합니다. MySQL connection 메모리
자원 차이는 다음 조건으로 비교했습니다.
- 설정: 같은 Spring 프로세스와 MySQL에서
maximumPoolSize=100을 고정하고minimumIdle만100 → 2 → 100으로 변경했습니다. 100은 차이를 관측하기 위한 실험값입니다. - 측정 상태: 모든 connection을 반환하고 실제 idle connection 수가 목표값에 도달한 뒤, application SQL 없이 각 상태를 30초 관측했습니다.
- 제외 범위: connection 생성, 정리와 진단 SQL은 CPU 측정 구간에서 제외했습니다.
maxLifetime30분, keepalive 비활성 상태여서 주기적인 교체와 확인 비용은 비교하지 않았습니다.
connection 수와 메모리 변화 비교하기
- DB connection과 socket FD: 100개에서 2개로 축소하면서 각각 98개 줄었습니다.
- 계측 메모리와 JVM Heap: 감소했습니다. MySQL은 application connection thread에 귀속된 Performance Schema 현재 사용량 합계이며, GC 요청 후 JVM Heap에는 Spring과 관측 코드도 포함됩니다. 정확한 connection별 메모리 비용은 아닙니다. MySQL 메모리 계측
- MySQL RSS(resident set size): 축소 직후 감소하지 않았습니다. 자원 정리와 메모리의 OS 반환은 구분해야 합니다.
CPU 절감 효과 확인하기
connection을 다시 늘렸을 때 CPU가 함께 증가하지 않아, 이번 관측에서는 idle connection 축소의 CPU 절감 효과를 확인하지 못했습니다.
- 막대: 각 상태의 30초 process CPU 평균입니다.
- 중앙 범위선: 연속된 10초 관측 3구간의 최소~최대이며, 독립된 실험 3회의 범위는 아닙니다.
- 세로축: MySQL과 JVM 패널의 범위가 다르며, CPU 1개를 100%로 계산했습니다.
idle connection 축소 후 첫 부하의 비용 비교하기
닫은 connection을 다시 만들 때는 TCP connection, 인증과 session 초기화가 필요하고, TLS를 사용하면 해당 설정 비용도 듭니다. 첫 부하에서는 이 생성 비용과 다른 요청이 반환한 connection을 기다리는 시간이 함께 발생할 수 있습니다.
- pool 설정:
maximumPoolSize=10에서minimumIdle0, 2, 기본값 10을 비교했습니다. 모두 10개까지 예열한 뒤 반환하고,idleTimeout을 10초로 줄여 실제 connection 수가 각각 0, 2, 10이 된 것을 확인했습니다. - 요청 부하: 40개 thread가 동시에 connection을 요청하고, 획득한 connection으로
SELECT SLEEP(0.020)을 실행했습니다. 획득 시간은getConnection()호출부터 connection을 받기까지이며 SQL 실행 시간은 제외했습니다. - 생성 지연: 추가 지연이 없는 조건과 physical connection 생성에만 100ms를 주입한 조건을 비교했습니다. 100ms는 실측 네트워크 지연이 아닙니다.
표는 3회 실험에서 각각 구한 connection 획득 p95의 중앙값이며, 괄호는 회차별 p95의 최소~최대입니다. 전체 요청을 합친 p95는 아닙니다.
minimumIdle | 추가 지연 없음 | 생성에 100ms 추가 |
|---|---|---|
| 0 | 255.23ms (255.18~274.70) | 563.37ms (547.38~564.23) |
| 2 | 184.84ms (176.14~186.26) | 309.44ms (307.34~309.84) |
| 기본값 10 | 71.81ms (66.57~73.80) | 82.81ms (72.31~83.80) |
HikariCP 5.1.0은 일반적인 pool 확장을 단일 생성 thread에서 처리하므로, 요청이 몰려도 상한까지 즉시 채워지지는 않습니다. connection 생성 구현
- 표시 구간: 생성에 100ms를 추가한 첫 회차의 첫 부하입니다. 두 패널은 같은 시간축이며 각 측정이 끝난 지점까지 표시했습니다.
- 관측 결과: idle connection 0개에서 시작한 pool은 요청 처리 중에도 상한 10개에 도달하지 않았고 pending thread가 발생했습니다.
같은 생성 지연에서 connectionTimeout=250ms로 줄이자 minimumIdle=0은 3회 모두 획득 실패가 발생했고, 기본값 10은 모두 없었습니다. 이 timeout은 pool에서 connection을 얻는 제한이며 SQL이나 transaction의 제한 시간이 아닙니다. 상세 조건과 원자료는 experiments/hikari-idle-burst/에 남겼습니다.
결론: 필요한 상한을 정하고 fixed-size pool을 우선하기
실제 SQL과 전체 instance의 DB connection 예산으로 maximumPoolSize를 정하고, 평시에도 그 크기를 유지할 수 있다면 minimumIdle=maximumPoolSize를 우선하겠습니다. HikariCP도 급격한 부하 대응을 위해 이 구성을 권장합니다. minimumIdle 권장 설정
SQL 실행 중 DB CPU나 lock wait가 늘었다면 SQL 비용과 connection usage time(빌린 뒤 반환할 때까지의 시간)을 확인해야 합니다. minimumIdle을 높여 해결할 문제는 아닙니다. 이번 SLEEP SQL 실험으로 실제 서비스의 최적 상한을 정할 수는 없습니다.
실험과 pool 동작을 바탕으로 상황별 조정 방향을 정리했습니다. 요청 유형별 운영 성능을 측정한 결과는 아닙니다.
| 상황 | 조정 방향 | 검증할 조건 |
|---|---|---|
| 꾸준한 요청 또는 예고 없는 급증, 첫 응답 지연에 민감 | 자원 예산 안에서 fixed-size pool 유지 | 기동 직후와 피크 첫 부하의 획득 대기 |
| 피크에도 pool 상한이 필요 이상으로 큼 | minimumIdle과 maximumPoolSize를 함께 축소 | 획득 대기와 SQL 지연이 목표 안에 드는지 확인 |
| 피크 상한은 필요하지만 긴 idle 시간의 보유 자원이 부담 | minimumIdle을 낮추고 idleTimeout 조정 | 축소 후 첫 부하의 획득 시간과 timeout |
| 정해진 시간에 배치 실행 | idle 구간에 축소한다면 시작 전에 connection 준비 | 준비 시간과 온라인 요청을 포함한 connection 예산 |
minimumIdle을 낮춰도 모든 pool이 상한까지 늘어날 가능성은 남습니다. DB connection 예산에는 rolling deployment 중 함께 실행되는 instance와 다른 서비스도 포함해야 합니다. fixed-size pool도 maxLifetime에 따른 교체나 장애 후 재연결은 발생합니다.