풀을 키우면 오히려 느려진다 — 직관을 배신하는 대표 사례.
-
증상 — 커넥션 대기가 보여서 풀을 100 → 300 으로 올렸다
- TPS 가 떨어지고 p99 가 나빠졌다
-
원인 — 병목은 커넥션 개수가 아니라 DB 의 실제 동시 처리 능력이었다
- DB 에 300개가 동시에 들어가면
- 컨텍스트 스위치 증가
- 락 경합 증가
- 버퍼 캐시 경쟁 → 디스크 I/O 증가
- DB 안에서 서로를 방해한다
- DB 에 300개가 동시에 들어가면
대기는 어디에서 하는 편이 나은가
- 풀에서 대기 — 순서대로 줄을 선다. DB 는 최적 부하로 계속 돈다
- 전체 처리량이 유지되고 지연이 예측 가능하다
DB 안에서 대기 모두가 조금씩 진행하며 서로를 방해한다
-
전체가 함께 느려진다 (호위 효과)
-
큐를 앞단에 두는 것이 시스템 전체에는 유리하다
출발점이 되는 공식
HikariCP 문서가 인용하는 경험식 풀 크기 = (코어 수 × 2) + 유효 스핀들 수
8코어 · SSD 기준 대략 20 내외에서 시작한다 "수백" 은 거의 언제나 잘못된 값이다
리틀의 법칙으로도 확인한다 필요한 동시성 = 처리량 × 평균 응답시간
- 1,000 TPS × 10ms = 10 — → 풀 10~20 이면 충분
데드락을 부르는 설정
한 요청이 커넥션을 2개 이상 잡는 코드가 있으면 (예: 트랜잭션 안에서 다른 데이터소스 조회)
필요한 최소 풀 크기 = 스레드 수 × (요청당 최대 커넥션 - 1) + 1
- 이보다 작으면 모두가 두 번째 커넥션을 기다리며 멈춘다
- 애초에 요청당 커넥션은 1개만 잡도록 설계하는 편이 낫다
반드시 함께 볼 타임아웃
- connectionTimeout — 풀에서 기다리는 최대 시간 — 무한 대기 금지
- maxLifetime — DB/방화벽의 유휴 종료보다 짧게 (안 그러면 죽은 커넥션을 잡는다)
- validationTimeout · leakDetectionThreshold 누수 추적
스레드 풀과 함께 봐야 한다
웹 스레드 200 · 커넥션 풀 20 이면
- 최대 180개가 커넥션을 기다린다
이때 대기 시간이 요청 타임아웃보다 길면
- 클라이언트는 이미 끊었는데 서버는 계속 처리한다 → 완전한 낭비
그래서 세 값을 함께 맞춘다
- 웹 스레드 수 · 커넥션 풀 크기 · 커넥션 대기 타임아웃
가상 스레드를 쓰면 웹 스레드 수 제한이 사라지므로 '앞단에서 자연히 걸리던 제동' 도 사라진다 → 커넥션 풀·세마포어로 명시적 제한이 필요하다
모니터링할 지표
- active / idle / pending — pending 이 지속적으로 0 보다 크면 부족 신호
- connection acquire 시간 p99 — 여기가 응답 지연의 원인인지 판별
- usage 시간 (빌린 시간) — 길면 커넥션을 오래 쥐고 있다는 뜻
- 트랜잭션 안에서 외부 API 를 부르고 있지 않은지 본다
가장 흔한 실수: 트랜잭션 안에서 HTTP 호출 커넥션을 수백 ms 씩 잡고 있어 풀이 마른다
면접 함정
- ❌ "대기가 보이면 풀을 늘린다" → 병목이 DB면 역효과다.
- ❌ "풀은 클수록 안전" → 부하가 몰릴 때 DB를 먼저 무너뜨린다.