minimumIdle을 maximumPoolSize와 같게 둔 고정 풀은 버스트 때 커넥션 생성 지연을 없앤다
요약
- HikariCP 기본값은
minimumIdle = maximumPoolSize(고정 풀)다. minimumIdle을 낮추면 idle 커넥션을 반납해 DB 자원을 아끼지만, 버스트 수요가 idle 수를 넘는 순간 커넥션을 새로 맺느라(TCP+TLS+인증 핸드셰이크, 수십 ms) 그 지연을 버스트를 유발한 요청이 직접 떠안는다 — 제일 바쁜 순간에 느려진다. - 단 멀티프로세스(gunicorn worker × 인스턴스)에선 고정 풀이 항상
프로세스 수 × 풀 크기만큼 DB 커넥션을 상시 점유한다. 그래서 풀 자체를 작게 역산한 뒤 그 작은 풀을 고정 풀로 채우는 게 절충이다.
본문
풀의 두 노브
maximumPoolSize는 풀이 가질 수 있는 커넥션 상한, minimumIdle은 놀고 있어도 유지할 최소 idle 커넥션 수다. 이 둘을 같게 두면 풀 크기가 고정된다(고정 풀). HikariCP는 기본값이 둘을 같게 잡고, 제작자는 minimumIdle을 따로 설정하지 말라고 권장한다.
minimumIdle < max로 두면 생기는 일 평소엔 idle 커넥션을 적게 유지하다가, 트래픽 버스트가 와서 동시 수요가 idle 수를 넘으면 HikariCP가 그 자리에서 커넥션을 새로 만든다. 커넥션 생성은 TCP 연결 + TLS + DB 인증 핸드셰이크라 수십 ms가 든다. 그리고 그 생성 지연은 버스트를 유발한 바로 그 요청이 지불한다. 즉 부하가 가장 몰리는 순간에, 하필 커넥션 만드느라 응답이 느려지는 역설이 생긴다.
그래서 고정 풀을 권장하는 이유 minimumIdle을 낮춰서 얻는 이득은 "idle 커넥션을 반납해 DB 자원을 아끼는 것"인데, 이 이득이 위의 버스트 응답시간 손해보다 대부분 작다는 판단이다. 미리 다 띄워두면 버스트가 와도 기다릴 필요 없이 즉시 꺼내 쓴다. 풀 크기가 고정이라 동작도 예측 가능해진다.
| 고정 풀 (min=max) | 탄력 풀 (min<max) | |
|---|---|---|
| 버스트 응답성 | 스파이크 없음 (이미 떠 있음) | 생성 지연 스파이크 |
| DB 자원 | 항상 max 점유 | idle 시 반납 |
| 예측 가능성 | 높음 | 낮음 |
멀티프로세스에선 전제가 깨질 수 있다 — 핵심 주의점 HikariCP의 "idle 반납 이득은 작다"는 전제는 "앱 한 프로세스에 풀 하나" 같은 단순 환경을 가정한다. 그런데 gunicorn 워커·다중 인스턴스 환경에선 풀이 프로세스마다 따로 뜬다.
상시 점유 = 인스턴스 수 × 워커 수 × 워커당 풀 크기
예: 10 × 4 × 100 = 4,000 커넥션을 항상 점유
워커당 풀에 고정 풀을 그대로 적용하면 idle 커넥션이 N배로 증폭돼 DB의 max_connections를 압박하고, MySQL이면 커넥션마다 per-thread 버퍼 메모리까지 먹는다. 이 환경에서 "워커당 100을 고정 풀"은 위험하다.
절충: 풀을 작게 역산한 뒤 그 작은 풀을 고정 풀로
풀 크기 자체를 DB max_connections 여유분 ÷ (인스턴스 × 워커)로 역산해 작게(예: 워커당 30~40) 잡으면 증폭 문제가 사라진다. 그렇게 작아진 풀에 대해서는 다시 고정 풀(full pre-warm)을 적용하는 게 자연스럽다 — 어차피 작으니 미리 다 띄워도 DB 부담이 적고, 버스트 레이턴시 스파이크는 없앨 수 있다. "풀을 작게 만들어 증폭을 잡고, 그 작은 풀은 꽉 채워 응답성을 확보"하는 두 단계다.
Python에는 minimumIdle 등가가 없다
HikariCP는 Java(JDBC) 라이브러리라 Python에선 쓰지 않는다. SQLAlchemy QueuePool은 커넥션을 요청 올 때마다 lazy하게 pool_size까지 만들 뿐, "미리 N개 띄워 유지"하는 minimumIdle 기능이 기본엔 없다. Python에서 pre-warm을 원하면 앱 시작 시 커넥션을 명시적으로 미리 여는 코드를 직접 넣어야 한다(gevent라면 monkey-patch가 import보다 먼저 실행돼야 함도 별개 전제).
관련 노트
- db connection pool의 max age에 jitter 를 적용하면 connection 생성 p99가 개선된다.
- 커넥션 풀 대기는 프로세스당 실사용량으로 풀 고갈과 재연결 churn을 가른다
- connection pool에서 connection 순환 속도을 고려하지 않으면 조용히 서비스 성능이 악화된다.
참고
- HikariCP README —
minimumIdle항목: https://github.com/brettwooldridge/HikariCP#frequently-used"This property controls the minimum number of idle connections that HikariCP tries to maintain in the pool. ... Default: same as maximumPoolSize" "However, for maximum performance and responsiveness to spike demands, we recommend not setting this value and instead allowing HikariCP to act as a fixed size connection pool."
- HikariCP Wiki — About Pool Sizing (커넥션이 많을수록 성능이 나빠지는 이유, Oracle Real-world Performance 분석): https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
- SQLAlchemy
QueuePool(Python 등가 풀, lazy 생성): https://docs.sqlalchemy.org/en/20/core/pooling.html