에디블로그
Engineer's Field Notes

AI 자동화로 매일
한 편씩 쓰는
엔지니어 운영 노트

Claude Code · 자동화 파이프라인 · 사고 회고까지. 잘 굴러간 기록 + 깨진 흔적도 같이 남깁니다.

사람이 할 수 있는 일은,
AI도 할 수 있어야 합니다.
매일 한 편 쓰면서 검증 중.
— 이번 주 가장 많이 읽힌 글 TOP 3
백엔드/데이터 & DB

[MySQL] HikariCP 커넥션 풀 설정: 풀 사이즈 공식, 타임아웃, 커넥션 누수 잡기

반응형
[MySQL] HikariCP 커넥션 풀 설정: 풀 사이즈 공식, 타임아웃, 커넥션 누수 잡기

[MySQL] HikariCP 커넥션 풀 설정: 풀 사이즈 공식, 타임아웃, 커넥션 누수 잡기

"Connection is not available, request timed out after 30000ms" — 트래픽이 몰리는 날 만나는 그 에러예요. 반사적으로 풀 사이즈를 늘리고 싶지만, 커넥션 풀의 세계에서는 크게가 아니라 작게가 정답인 경우가 많아요. 반직관적이라 원리를 알아야 해요.

이 글은 커넥션 풀이 왜 필요한지부터, HikariCP(스프링 부트 기본) 핵심 설정, 풀 사이즈 공식과 그 반직관, 커넥션 누수 진단, 그리고 풀 부족의 진짜 원인 찾기까지 정리해요.

01. 풀이 하는 일

커넥션 풀 동작 구조. HikariCP가 커넥션을 미리 만들어두고 요청 스레드에 빌려주며, close는 끊기가 아니라 반납이라 커넥션이 풀로 돌아가 재사용된다

DB 커넥션 하나를 새로 만드는 건 비싸요 — TCP 연결, 인증, 세션 초기화까지 수십 ms예요. 매 쿼리마다 이걸 하면 쿼리보다 연결이 더 오래 걸려요. 그래서 풀이 커넥션을 미리 만들어두고 빌려줘요. 코드에서 close()를 불러도 끊는 게 아니라 풀에 반납하는 거고요.

핵심 설정은 몇 개 안 돼요.

spring:
  datasource:
    hikari:
      maximum-pool-size: 20        # 최대 커넥션 수 (기본 10)
      connection-timeout: 3000     # 풀에서 못 빌리면 3초 후 에러 (기본 30초)
      max-lifetime: 1800000        # 커넥션 수명 30분 (DB wait_timeout보다 짧게)
      leak-detection-threshold: 30000  # 30초 미반납 시 누수 의심 로그
두 가지 기본값은 손보는 게 좋아요. connection-timeout 기본 30초는 너무 길어요 — 풀이 마르면 요청들이 30초씩 매달려 있다가 한꺼번에 죽어요. 3~5초로 줄여 빨리 실패하고 빨리 드러나게 해요. max-lifetime은 MySQL의 wait_timeout보다 확실히 짧아야 해요. DB가 먼저 끊은 죽은 커넥션을 풀이 빌려주면 "Communications link failure"가 간헐적으로 터져요.

02. 풀 사이즈, 크게가 아니라 작게

커넥션 풀 사이징의 반직관. 풀을 100개로 늘리면 DB 코어는 그대로인데 동시 실행만 늘어 쿼리당 시간이 길어지는 악순환이고, DB 코어의 2배 안팎의 작은 풀이 회전율을 높여 같은 처리량을 처리한다

"부족하니 늘리자"가 왜 악수가 되냐면, DB가 동시에 처리할 수 있는 양은 결국 DB 서버의 코어 수에 묶여 있거든요. 코어 16개에 커넥션 100개를 던지면, 100개 쿼리가 16개 코어를 두고 컨텍스트 스위칭하며 경쟁해요. 쿼리당 처리 시간이 길어지고 → 커넥션 점유가 길어지고 → 풀이 더 부족해지는 악순환이에요. DB 메모리도 커넥션당 소모되고요.

HikariCP 공식 문서의 권장 출발점이 유명해요 — (DB 코어 수 × 2) 안팎. 원형 공식은 connections = (core_count × 2) + effective_spindle_count인데, SSD에선 스핀들 항이 사실상 무의미해서 코어 × 2만 남겨 쓰는 거예요. 16코어 DB면 풀 30 전후가 출발점인데, 이건 DB가 받는 총량이에요 — 앱 인스턴스들이 이 총량을 나눠 가져요(인스턴스 5대면 각 6~10이지, 인스턴스당 30이 아니에요). 실제로 작은 풀이 더 높은 처리량을 내는 벤치마크가 HikariCP 위키에 있어요.

그리고 풀 부족 신호가 올 때 늘리기 전에 물어야 할 질문 — "왜 커넥션을 오래 점유하지?" 진범은 대부분 이 셋이에요.

  • 느린 쿼리 — 인덱스 문제로 1초씩 걸리는 쿼리가 풀을 말려요. EXPLAIN부터.
  • 트랜잭션 안의 외부 호출 — API 응답 2초를 기다리는 동안 커넥션을 쥐고 있어요. 호출을 트랜잭션 밖으로.
  • OSIV — 응답 끝까지 커넥션을 잡는 그 패턴이에요. OSIV 편에서 본 대로요.

03. 커넥션 누수, 반납 안 하는 코드 잡기

커넥션 누수 진단. 예외 경로의 close 누락이나 미종료 트랜잭션으로 커넥션이 반납되지 않으면 가용 커넥션이 점점 줄다 타임아웃 장애가 난다. leakDetectionThreshold를 켜면 미반납 코드의 스택트레이스가 로그에 남는다

풀 부족의 또 다른 갈래는 누수예요. 어딘가의 코드가 커넥션을 빌리고 안 돌려주는 거예요. 스프링 관리 영역에서는 드물지만, 수동 DataSource 사용, 예외 경로의 자원 정리 누락, 끝나지 않는 트랜잭션에서 생겨요. 증상이 특징적이에요 — active 커넥션은 높은데 실제 도는 쿼리는 없고, 시간이 지날수록 가용 커넥션이 줄어들다 어느 날 장애가 나요. 재시작하면 잠시 멀쩡해지고요.

잡는 도구가 leak-detection-threshold예요. 지정 시간(예: 30초) 이상 반납 안 된 커넥션이 있으면, 그걸 빌려간 코드의 스택트레이스를 로그로 찍어줘요. 범인이 로그에 그대로 나오는 거죠. 운영에서 상시 켜두기 부담스러우면 의심될 때만 켜도 돼요.

04. 모니터링할 지표

HikariCP는 Micrometer로 지표를 내보내요. 대시보드에 셋만 띄워도 충분해요.

  • active — 사용 중 커넥션. max에 자주 닿으면 위 "오래 점유" 원인 분석부터예요.
  • pending — 커넥션을 기다리는 스레드 수. 0이 아니면 이미 부족하다는 뜻이에요.
  • usage(점유 시간) — 커넥션 한 번 빌림의 평균 시간. 이게 길어지는 추세면 느린 쿼리·트랜잭션 비대를 의심해요.

"DB는 한가한데 active만 높다"는 OSIV·외부 호출 패턴, "DB도 바쁘고 usage도 길다"는 쿼리 튜닝 — 지표 조합으로 방향이 갈려요.

05. 자주 만나는 문제

connection timeout이 간헐적으로 나요

트래픽 피크에 풀이 마르는 거예요. pending·usage 지표로 "오래 점유" 원인(느린 쿼리·외부 호출·OSIV)을 먼저 잡고, 그래도 부족하면 그때 사이즈를 조정해요.

Communications link failure가 가끔 터져요

DB가 끊은 커넥션을 풀이 빌려준 거예요. max-lifetime을 MySQL wait_timeout보다 짧게(여유 있게 30초 이상 차이) 잡아요.

재시작하면 괜찮다가 며칠 지나면 또 말라요

전형적인 누수예요. leak-detection-threshold를 켜고 로그의 스택트레이스로 미반납 코드를 찾아요.

정리

커넥션 풀의 핵심은 "작은 풀 + 빠른 회전"이에요. 사이즈는 DB 코어 기준으로 작게 출발하고, 부족 신호가 오면 늘리기 전에 점유 시간(느린 쿼리·트랜잭션 안 외부 호출·OSIV)부터 줄여요. connection-timeout은 짧게, max-lifetime은 DB보다 짧게, 누수는 leak-detection-threshold로 — 이 네 줄이면 커넥션 장애의 대부분이 예방돼요.

그래서 "connection timeout" 알림이 왔을 때 풀 사이즈에 손대기 전에 던질 질문은 하나예요. "이 커넥션들이 지금 무엇을 하느라 오래 점유되고 있나?" 그 답을 못 찾은 채 늘리는 풀은 악순환의 시작이에요. 이제 마지막 편에서, 이런 증상들을 한데 모아 느린 쿼리 트러블슈팅 허브로 시리즈를 닫아요.

출처: HikariCP — About Pool Sizing · Spring Boot — DataSource Connection Pool

반응형

📚 같이 보면 좋은

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 일정액의 수수료를 제공받습니다."