Spring Boot + MySQL + K8s 타임아웃

Spring Boot 3.4 + MySQL 8.4 + Kubernetes: idle connection timeout 장애 재현과 복구 가이드 요약 이미지
요약 이미지(직접 생성). 무단 사용 금지.

Spring Boot 3.4 + MySQL 8.4 + Kubernetes에서 idle connection timeout 장애를 재현하고 복구하는 운영 가이드

이 글은 Spring Boot, HikariCP, MySQL, Kubernetes를 함께 운영할 때 자주 만나는 연결 끊김 이슈를 공식 문서 근거로 정리한 실무 가이드입니다. 아이들 상태가 길어진 뒤 첫 쿼리에서 실패하는 패턴을 재현하고, 풀 설정·DB 설정·프로브 설정을 어떻게 맞춰야 하는지 단계별로 설명합니다.

1) 버전/전제

  • Spring Boot: 3.4.x 계열 (공식 릴리즈 노트 기준)
  • Connection Pool: HikariCP (Spring Boot 기본 DataSource 풀)
  • Database: MySQL 8.4 (Reference Manual 시스템 변수/타임아웃 기준)
  • Runtime: Kubernetes Deployment + readiness/liveness probe 사용
  • 장애 가정: DB가 idle 연결을 먼저 끊고, 애플리케이션 풀은 아직 연결이 살아있다고 판단하는 불일치

핵심 전제는 단순합니다. DB의 연결 생존 시간보다 풀의 연결 재사용 수명이 길면, 오래된 연결을 꺼내 쓰는 순간 실패 확률이 올라갑니다.

2) 핵심 개념

HikariCP 수명/유휴 제어: HikariCP는 maxLifetime, idleTimeout, keepaliveTime 같은 설정으로 연결의 생애주기를 제어합니다. 공식 문서에 따르면 maxLifetime은 풀 내부에서 연결을 교체하는 상한이며, 외부 시스템(DB/네트워크)이 먼저 끊기기 전에 교체되도록 잡는 것이 일반 원칙입니다.

MySQL idle timeout: MySQL은 세션 유휴 시간을 기준으로 연결을 종료할 수 있으며, 대표적으로 wait_timeoutinteractive_timeout 변수가 동작에 관여합니다. 서버가 먼저 소켓을 닫으면 애플리케이션 쪽에는 다음 쿼리 시점에 통신 오류로 관측됩니다.

Kubernetes probe의 책임 경계: readiness/liveness probe는 프로세스 생존·트래픽 수용 여부를 판단하지, DB 연결 풀 내부의 개별 커넥션 품질까지 보장하지 않습니다. 즉 probe가 정상이어도 첫 트랜잭션이 실패할 수 있습니다.

3) 트레이드오프

  • maxLifetime을 짧게: stale connection 위험이 줄지만, 연결 재생성 빈도가 올라가 DB/네트워크 부하가 증가할 수 있습니다.
  • keepaliveTime 활성화: 유휴 연결의 사전 점검 효과가 있지만, 주기적 ping 쿼리 비용이 생깁니다.
  • DB wait_timeout을 길게: 앱 측 부담은 줄지만, 유휴 세션 자원 점유가 늘어날 수 있습니다.
  • readiness를 보수적으로: 장애 노출을 줄이지만, 배포·스케일링 시 Ready 전환이 느려질 수 있습니다.

실무에서는 보통 DB timeout보다 Hikari maxLifetime을 더 짧게 두고, 필요 시 keepaliveTime을 추가해 균형을 맞춥니다.

4) 장애 재현/해결

재현 시나리오: MySQL wait_timeout을 낮게(예: 60초) 두고, Hikari maxLifetime을 더 길게 유지한 상태에서 트래픽 공백을 만든 뒤 첫 쿼리를 실행합니다. 이때 통신 오류(예: connection is closed/communications failure 계열)가 간헐적으로 발생할 수 있습니다.

관측 포인트: 애플리케이션 로그(첫 쿼리 실패 시점), DB 에러 로그(세션 종료), 풀 메트릭(활성/유휴/획득 대기) 3가지를 같은 타임라인으로 맞추면 원인 분리가 빨라집니다.

해결 절차:

  • 1단계: DB wait_timeout 값을 확인합니다.
  • 2단계: Hikari maxLifetime을 wait_timeout보다 짧게 조정합니다.
  • 3단계: 유휴 구간이 긴 워크로드라면 keepaliveTime을 검토합니다.
  • 4단계: readiness probe가 실제 트래픽 준비 상태를 반영하는지 재점검합니다.
  • 5단계: 조정 후 동일한 유휴 패턴으로 재현 테스트를 반복해 오류 재발 여부를 확인합니다.

핵심은 단일 값 튜닝이 아니라, DB timeout·풀 수명·프로브 전략을 같은 수명 모델로 정렬하는 것입니다.

5) 체크리스트

  • MySQL wait_timeout/interactive_timeout 현재값을 운영 환경에서 확인했는가
  • Hikari maxLifetime이 DB idle timeout보다 충분히 짧은가
  • 필요 시 keepaliveTime을 활성화했고, DB 부하 증가를 모니터링하는가
  • readiness probe가 단순 프로세스 생존이 아니라 실제 의존성 준비 상태를 반영하는가
  • 장애 재현 테스트(유휴 후 첫 요청)를 배포 전/후 동일 시나리오로 수행했는가
  • 로그·메트릭·DB 이벤트를 같은 타임라인으로 상호 검증했는가

6) 공식 링크

7) 커넥션 풀 타임아웃 설정 종합 예시

Spring Boot + MySQL + K8s 환경에서 가장 많이 발생하는 장애가 커넥션 타임아웃입니다. 3곳의 타임아웃을 일관되게 설정해야 합니다.

# application.yml — HikariCP + MySQL 타임아웃 설정
spring:
  datasource:
    url: jdbc:mysql://mysql-service:3306/mydb?connectTimeout=5000&socketTimeout=30000
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 5000      # 풀에서 커넥션 대기 최대 5초
      idle-timeout: 600000           # 유휴 커넥션 10분 후 제거
      max-lifetime: 1800000          # 커넥션 최대 수명 30분
      validation-timeout: 3000       # 유효성 검증 3초
      leak-detection-threshold: 30000  # 30초 이상 반환 안 하면 경고

---
# MySQL my.cnf — 서버 측 타임아웃
[mysqld]
wait_timeout = 3600              # 유휴 커넥션 1시간 후 종료
interactive_timeout = 3600
max_connections = 200
connect_timeout = 10

---
# K8s Service — 타임아웃 체인
# Pod → Service → Ingress 각 단계 타임아웃 확인
# 규칙: Ingress > Service > App > DB 순으로 타임아웃이 커야 함

타임아웃 체인 규칙

Ingress (60s) > Spring Boot (30s) > HikariCP (5s) > MySQL (3600s idle)
     ↑                ↑                ↑                ↑
  클라이언트 대기    요청 처리       풀 대기         서버 유휴 정리

⚠️ 주의: MySQL wait_timeout < HikariCP max-lifetime이면
   앱이 이미 닫힌 커넥션을 사용 → "Communications link failure"

8) K8s에서 MySQL 연결 장애 디버깅

# 1. Pod에서 MySQL 연결 테스트
kubectl exec -it api-server-xxx -- bash
mysql -h mysql-service -u root -p -e "SELECT 1"

# 2. HikariCP 메트릭 확인 (Actuator)
curl http://localhost:9090/actuator/metrics/hikaricp.connections.active
curl http://localhost:9090/actuator/metrics/hikaricp.connections.pending

# 3. MySQL 현재 커넥션 상태
mysql -e "SHOW STATUS LIKE 'Threads_connected';"
mysql -e "SHOW STATUS LIKE 'Aborted_connects';"
mysql -e "SHOW PROCESSLIST;"

# 4. 네트워크 레벨 확인
kubectl exec api-server-xxx -- nslookup mysql-service
kubectl exec api-server-xxx -- nc -zv mysql-service 3306

9) 관련 글

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux