배경
부하 테스트 중 Zipkin 트레이스에서 connection 스팬이 220ms 찍히는 현상을 발견. HikariCP 동작 방식과 cold start 원인을 파악함.
HikariCP란
Spring Boot 기본 JDBC 커넥션 풀. 애플리케이션 ↔ DB 사이의 TCP 연결을 미리 만들어두고 재사용해서 매 요청마다 연결 비용을 없애는 역할.
Cold Start가 발생하는 이유
HikariCP는 기본적으로 lazy 초기화를 사용함. 앱이 뜰 때 커넥션을 미리 만들지 않고, 첫 요청이 들어올 때 비로소 물리 TCP 연결을 생성함.
앱 시작 → 풀 생성 (빈 상태)
첫 요청 → 그때서야 PostgreSQL TCP 연결 생성 → ~220ms
이후 요청 → 이미 만들어진 커넥션 재사용 → ~0ms
lazy가 기본값인 이유: 앱이 뜨자마자 트래픽이 없을 수도 있어서 DB 자원을 미리 점유하지 않기 위함. 서비스가 수십 개면 idle 커넥션 수백 개가 DB를 점유하는 낭비가 생길 수 있음.
Eager 초기화로 변경하려면
spring:
datasource:
hikari:
initialization-fail-timeout: 30000 # 시작 시 커넥션 생성, 최대 30초 대기
앱 시작 시간이 늘어나는 대신 첫 요청부터 0ms.
커넥션 생명주기

핵심: minimumIdle 덕분에 idleTimeout으로 커넥션이 제거돼도 백그라운드에서 즉시 새 커넥션을 만들어 풀을 채움. 다음 요청이 올 때는 이미 준비된 커넥션이 있어서 cold start 없음.
커넥션은 유저별이 아님
흔히 오해하는 부분. HikariCP 커넥션은 서비스 ↔ DB 사이의 연결이지, 특정 유저에 귀속되는 게 아님.
유저 A 요청 → 풀에서 커넥션 꺼냄 → 쿼리 → 커넥션 반납
유저 B 요청 → 같은 커넥션 재사용 → 쿼리 → 커넥션 반납
유저가 달라도 이미 만들어진 커넥션을 공유해서 씀. cold start는 유저 기준이 아니라 앱 재시작 기준.
동시 요청과 풀 사이즈
동시 요청이 들어오면 각각 커넥션 1개씩 사용.
VU 1 → 커넥션 1 사용 중
VU 2 → 커넥션 2 사용 중
VU 3 → 커넥션 3 사용 중
maxPoolSize(기본 10)를 초과하는 동시 요청이 오면 커넥션이 반납될 때까지 대기함 → 트레이스에 connection 대기 시간 증가로 나타남.
커넥션 풀이 존재하는 근본 이유
커넥션 생성 비용 > 유지 비용 이기 때문.
생성 비용: TCP 핸드셰이크 + PostgreSQL 인증 → ~220ms (크다)
유지 비용: TCP 소켓 1개 + 메모리 약간 → 거의 없다
한 번 만들고 계속 재사용하는 게 이득. 이게 커넥션 풀이 존재하는 이유 자체.
minimumIdle vs maximumPoolSize 설정
maximumPoolSize = 10 (최대 10개까지 만들 수 있음)
minimumIdle = 2 (평소엔 2개만 유지)
- 트래픽 없을 때 → idle 커넥션 8개 제거 → 2개만 유지 (자원 절약)
- 트래픽 몰릴 때 → 최대 10개까지 늘림
Spring Boot 기본값: minimumIdle = maximumPoolSize = 10 → 항상 10개 유지 → 자원 낭비지만 항상 빠름.
minimumIdle 낮게 설정 시 cold start 재발 가능
minimumIdle = 2, maximumPoolSize = 10
평소: 커넥션 2개 유지
갑자기 요청 5개 동시에 들어옴
→ 2개는 즉시 처리
→ 나머지 3개는 커넥션 새로 생성 필요 → cold start 재발

cold start를 완전히 없애려면 minimumIdle = maximumPoolSize 가 답.
idleTimeout 이후 cold start가 없는 이유 (타이밍 차이)
Cold start (앱 재시작): 요청이 먼저 오고, 그때서야 커넥션 생성 → 요청이 기다림
풀 비어있음 → 요청 도착 → 커넥션 생성 (~220ms) → 처리
idleTimeout 이후: 커넥션 제거되자마자 백그라운드에서 미리 교체 → 요청이 올 때 이미 준비됨
커넥션 제거 → 백그라운드에서 즉시 새 커넥션 생성 → 요청 도착 → 0ms
단, 이는 minimumIdle이 설정된 경우에만 해당. minimumIdle = 0이면 교체 안 함.
커넥션 유지의 트레이드오프
- 유지 비용: TCP 소켓 점유, PostgreSQL은 커넥션 1개당 프로세스 1개 생성 (~5~10MB)
- 매번 연결 비용: TCP 핸드셰이크 + PostgreSQL 인증 = 수십~수백ms/요청
HikariCP는 maxPoolSize로 커넥션 수를 제한해 DB 자원 낭비를 막으면서, 재사용으로 연결 비용을 제거하는 균형점.
실제 경험 (부하 테스트)
- smoke 테스트 (5 VU) 첫 실행 → 응답 avg 896ms
- Zipkin 분석: coupon-service: connection 220ms, user-service 호출로 gateway 550ms 지연
- 원인: coupon-service + user-service 둘 다 cold HikariCP
- load 테스트부터는 JVM 워밍업 + 풀 초기화 완료 → 응답 대폭 감소 예상
'Study' 카테고리의 다른 글
| TIL - JVM Cold Start: 왜 첫 요청은 항상 느린가 (0) | 2026.08.07 |
|---|---|
| TIL - ExecutorService와 CountDownLatch로 동시성 테스트하기 (0) | 2026.07.29 |
| TIL - MSA에서 HTTP vs Kafka, Spring Cloud Gateway (0) | 2026.07.23 |
| TIL - Docker restart 정책 비교 (no / on-failure / always / unless-stopped) (0) | 2026.07.22 |
| TIL - Boolean.TRUE.equals()를 쓰는 이유 (0) | 2026.07.21 |