이 글에서 얻는 것
- “DB가 느리다”를 슬로우 쿼리/락 대기/커넥션 풀 고갈 중 어디 문제인지 분류할 수 있습니다.
- 슬로우 쿼리 로그를 켜고, 상위 쿼리를 기반으로 인덱스/쿼리/스키마 개선을 반복하는 루틴을 만들 수 있습니다.
- HikariCP 풀 사이즈/타임아웃을 “감”이 아니라 근거로 조정할 수 있습니다.
0) 튜닝은 ‘측정 → 가설 → 변경 → 재측정’이다
가장 흔한 실수는 “파라미터 먼저 바꾸기”입니다. 먼저 문제를 분류하면 절반은 해결됩니다.
- 특정 쿼리만 느리다 → 쿼리/인덱스/플랜
- 전반적으로 느리고 대기가 많다 → 락/트랜잭션/DB 자원
- 요청이 몰리면 타임아웃이 난다 → 커넥션 풀/DB max connections/스레드 풀
1) 슬로우 쿼리 로그: 상위 N개부터 잡자
1-1) 활성화(개념)
slow_query_log=1long_query_time=1(초 단위, 환경에 맞게)
실무 팁:
- 초기에는 threshold를 낮게 잡기보다 “운영에 부담이 없는 수준”에서 시작하고,
- 상위 쿼리/피크 시간대에서 패턴을 잡아가는 게 안전합니다.
1-2) 슬로우 로그를 읽는 관점
- 얼마나 자주 호출되는가(빈도)
- 평균/최악 지연이 어느 정도인가
- rows examined(많이 읽는가)
- 락 대기 시간이 포함되는가
느린 쿼리는 대개 이 3가지 중 하나입니다.
- 인덱스를 못 타서 많이 읽는다
- 조인/정렬/그룹핑이 비싸다(filesort/temporary)
- 락 대기 때문에 늦다(트랜잭션/동시성 문제)
2) 커넥션 풀(HikariCP): “크면 좋다”가 아니다
커넥션 풀은 DB 연결을 재사용해서 비용을 줄이지만, 풀을 과도하게 키우면 오히려 악화될 수 있습니다.
- DB가 처리할 수 있는 동시 쿼리 한도를 넘으면 큐잉/락 경합/컨텍스트 스위칭이 늘어납니다.
- 애플리케이션 스레드가 풀을 기다리며 요청 지연이 커집니다.
핵심 파라미터(자주 만지는 것):
maximumPoolSize: 동시에 빌릴 수 있는 커넥션 최대minimumIdle: 유휴 커넥션 유지 수(환경에 따라)connectionTimeout: 커넥션을 못 빌리면 얼마나 기다릴지(무제한 금지)maxLifetime: 커넥션 재생성 주기(DB/네트워크 장비의 idle timeout보다 짧게)
예시:
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.connection-timeout=2000
실무 감각:
- 풀 사이즈는 “서버 스레드 수”가 아니라, DB의 처리 능력/커넥션 한도와 함께 결정합니다.
connectionTimeout이 자주 터지면 “풀을 키우기” 전에 DB가 병목인지부터 확인해야 합니다.
3) 타임아웃/고립 커넥션: 운영에서 자주 터지는 포인트
- DB/프록시/LB가 커넥션을 끊었는데 애플리케이션이 모르고 재사용 → 간헐적 오류
maxLifetime/keepalive로 회피하거나, 인프라 idle timeout과 정합을 맞춰야 합니다
4) 튜닝 루틴(최소)
- 슬로우 로그/메트릭으로 상위 쿼리를 찾는다
- EXPLAIN으로 플랜을 본다(인덱스/rows/extra)
- 인덱스/쿼리 리라이트/스키마를 최소 변경으로 개선한다
- 풀/타임아웃을 “문제 원인”에 맞게 조정한다
- p95/p99 지연과 DB 지표를 재측정한다
연습(추천)
- 로컬/스테이징에서 슬로우 로그를 켜고 “상위 5개 쿼리”를 뽑아 인덱스 개선 전/후를 비교해보기
- Hikari 풀 사이즈를 크게/작게 바꿔보고, 처리량/지연/DB CPU가 어떻게 변하는지 관찰해보기
connectionTimeout이 발생하도록 부하를 주고, “풀 고갈 vs DB 병목”을 로그/지표로 구분해보기
💬 댓글