HikariCP 커넥션 풀 고갈 문제 해결기

Daniel·2025년 6월 17일

DataBase

목록 보기
5/5

필자가 담당하고 있는 프로젝트에서 간헐적으로 발생하는 커넥션 풀 고갈 문제를 체계적으로 분석하고 해결한 경험을 공유합니다.

어느 날 모니터링 시스템에서 다음과 같은 에러가 간헐적으로 발생하기 시작했다.

  • 오류 로그
HikariPool-1 - Connection is not available, request timed out after 30012ms 
(total=15, active=15, idle=0, waiting=0)
  1. 이용자가 제한적인 서비스임에도 불구하고 15개 커넥션이 모두 고갈
  2. 간헐적 발생으로 재현이 어려움
  3. total=15, active=15, idle=0 → 모든 커넥션이 사용 중인 상태
  4. 서비스 재시작 후에는 일시적으로 정상 동작
  • 프로젝트 설정(.yaml)
spring:
  datasource:
    hikari:
      connection-timeout: 30000
      maximum-pool-size: 15
      minimum-idle: 5
      idle-timeout: 300000
      max-lifetime: 600000

가설 1: 커넥션 리크 (Connection Leak)

  • 커넥션을 제대로 반환하지 않는 코드가 있을 가능성
  • 이용자가 제한적인데도 커넥션 풀이 고갈되는 것은 비정상적

가설 2: 장기 실행 쿼리

  • 일부 쿼리가 오래 실행되어 커넥션을 점유하고 있을 가능성

가설 3: DB 서버 성능 이슈

  • DB 서버 측 처리 지연으로 커넥션 반환이 늦어질 가능성

우선순위가 가장 높은 커넥션 리크부터 조사하기로 결정했다.

커넥션 리크 감지 설정 추가:

spring:
  datasource:
    hikari:
      leak-detection-threshold: 30000  # 30초 후 리크로 판단

체계적 코드 리뷰 계획:

  • @Transactional 없이 직접 커넥션을 다루는 코드 점검
  • try-with-resources 패턴을 사용하지 않은 JDBC 코드 확인
  • finally 블록에서 커넥션을 제대로 닫지 않는 코드 확인

결정적 발견!

리크 감지 설정을 적용한 날, 원인을 찾았다.

  • 리크 감지 로그
2025-06-17 09:34:41 [HikariPool-1 housekeeper] WARN c.zaxxer.hikari.pool.ProxyLeakTask 
- Connection leak detection triggered for com.mysql.cj.jdbc.ConnectionImpl@c67a89 
on thread http-nio-8080-exec-1
  • 문제 코드
// ❌ 문제: EntityManager 생성 후 close() 누락
public void updateDocInfo(String templateId, String docGroupName, 
                         String title, String guide) {
    EntityManager em = emf.createEntityManager();  // 커넥션 획득
    em.getTransaction().begin();
    
    Template temp = em.find(Template.class, templateId);
    temp.setDocGroupName(docGroupName);
    temp.setTitle(title);
    temp.setGuide(guide);
    
    em.getTransaction().commit();
    // ❌ em.close() 누락! → 커넥션이 HikariCP로 반환되지 않음
}

문제 분석:

  • EntityManager를 직접 생성했지만 close()를 호출하지 않음
  • 트랜잭션은 커밋되지만 커넥션은 풀로 반환되지 않음
  • 호출할 때마다 커넥션 하나씩 리크 발생

해결 과정

해결 방법: @Transactional 적용

Spring의 트랜잭션 관리 기능을 활용하여 EntityManager 생명주기를 Spring이 자동으로 관리하도록 변경

// ✅ 해결: Spring이 EntityManager 생명주기 자동 관리
@Transactional
public void updateDocInfo(String templateId, String docGroupName, 
                         String title, String guide) {
    Template template = selectFrom(QTemplate.template)
        .where(QTemplate.template.templateId.eq(Long.valueOf(templateId)))
        .fetchOne();
    
    if (template == null) {
        throw new CustomApiException(CustomErrorCode.DATA_IS_NULL,
            "템플릿을 찾을 수 없습니다. with id: " + templateId);
    }

    template.setDocGroupName(docGroupName);
    template.setTitle(title);
    template.setGuide(guide);
    // ✅ Spring이 자동으로 커넥션 반환 처리
}

추가 개선사항

  1. QueryDSL 도입: 타입 안전성 확보 및 컴파일 타임 오류 감지
  2. 예외 처리 강화: 데이터 검증 로직 추가
  3. 모니터링 설정 영구 적용: 향후 문제 예방

해결 결과

성과

  • 커넥션 리크 완전 해결: 더 이상 타임아웃 에러 발생하지 않음
  • 시스템 안정성 확보: HikariCP 커넥션 풀 정상 동작
  • 코드 품질 향상: QueryDSL 도입으로 타입 안전성 개선
  • 모니터링 체계 구축: 리크 감지 설정으로 향후 문제 예방

모니터링 결과

# 해결 후 정상적인 커넥션 풀 상태
HikariCP Metrics:
- Active connections: 2-5 (정상 범위)
- Idle connections: 5-10 (정상 범위)  
- Connection timeout: 0건 (문제 해결!)

학습한 내용과 회고

왜 이런 실수를 했을까?

  1. 순수 JPA 학습 배경: Spring 없이 JPA를 학습할 때의 패턴을 잘못 적용
  2. Spring 생태계 이해 부족: @Transactional의 EntityManager 자동 관리 기능을 몰랐음
  3. 리소스 관리 의식 부족: EntityManager도 AutoCloseable 리소스임을 간과

얻은 교훈

1. Spring vs 순수 JPA 환경의 차이

환경EntityManager 관리 방법
Spring 환경@Transactional 사용하여 Spring이 자동 관리
순수 JPA 환경try-with-resources 또는 finally에서 명시적 close()

2. 체계적인 문제 해결 접근법의 중요성

  • 가설 설정 → 우선순위 결정 → 체계적 검증
  • 모니터링 도구 활용의 중요성

3. 예방 조치의 필요성

  • 코드 리뷰 시 리소스 관리 체크리스트 적용
  • 커넥션 풀 모니터링 대시보드 구축

재발 방지책

# 운영 환경에 영구 적용한 모니터링 설정
spring:
  datasource:
    hikari:
      leak-detection-threshold: 30000
      
logging:
  level:
    com.zaxxer.hikari.pool.ProxyLeakTask: WARN

코드 리뷰 체크리스트에 추가:

  • EntityManager 직접 사용 시 반드시 close() 호출 확인
  • @Transactional 적용 가능 여부 검토
  • try-with-resources 패턴 적용 확인

관련 기술 스택 정리

이번 문제 해결 과정에서 학습한 기술들의 연관관계:

Application (@Transactional)
    ↓
Spring Transaction Manager
    ↓
JPA EntityManager
    ↓
HikariCP Connection Pool
    ↓
MySQL Database

마무리

이번 경험을 통해 단순히 기능을 구현하는 것을 넘어서, 시스템의 안정성과 성능을 고려한 개발의 중요성을 깨달았다.
특히 체계적인 문제 분석과 모니터링의 중요성, 그리고 Spring 생태계에 대한 깊은 이해가 얼마나 중요한지 실감했다.
앞으로도 이런 실무 경험을 통해 더욱 견고한 시스템을 구축하는 개발자로 성장해야겠다.

profile
응애 나 애기 개발자

0개의 댓글