99%가 모른다는 DB Connection 누수 문제
Kurly 팀이 MariaDB에서 MySQL로 JDBC 드라이버 변경 후 발생한 주기적 OOM 현상을 AbandonedConnectionCleanupThread 비활성화로 해결
AI 요약
Context
Kurly 팀은 JDBC 드라이버를 MariaDB에서 MySQL(mysql-connector-j)로 변경한 후 AWS EC2 인스턴스가 Out Of Memory로 인해 주기적으로 종료되는 현상을 경험했다. Heap Dump 분석 결과 AbandonedConnectionCleanupThread의 connectionFinalizerPhantomRefs에 13,689개의 ConnectionImpl 인스턴스가 누적되어 있었다.
Technical Solution
- mysql-connector-j의 PhantomReference 메커니즘 분석: Connection 생성 시 AbandonedConnectionCleanupThread가 PhantomReference 인스턴스를 connectionFinalizerPhantomRefs에 저장하고, GC 수거 전에 네트워크 리소스를 명시적으로 정리하는 구조 파악
- 병목 원인 식별: AbandonedConnectionCleanupThread가 단일 스레드(newSingleThreadExecutor)에서 ReferenceQueue를 polling하며 한 번에 하나의 네트워크 연결 종료 작업 수행
- 설정 값 검토: max-lifetime을 기본값 30분 대신 50초로 설정하여 Connection 생성 속도가 정리 속도를 36배 초과
- mysql-connector-j 8.0.22 이상 업그레이드: AbandonedConnectionCleanupThread 비활성화 옵션 추가된 버전으로 마이그레이션
- JVM 옵션 추가: -Dcom.mysql.cj.disableAbandonedConnectionCleanup=true 옵션 설정하여 AbandonedConnectionCleanupThread 비활성화
Impact
Heap Dump 재분석 결과 AbandonedConnectionCleanupThread에 의한 메모리 점유가 사라졌으며, 배포 이후 수 개월간 안정적으로 서비스가 운영 중이다.
Key Takeaway
DB Connection 누수는 트래픽 급증 문제로 착각하기 쉬우므로, CPU 또는 메모리 사용량 증가 장애 발생 시 Heap Dump 분석을 우선적으로 수행해야 한다. mysql-connector-j 사용 시 max-lifetime이 짧으면 단일 스레드 기반의 네트워크 정리 메커니즘이 병목이 될 수 있다.
실천 포인트
HikariCP와 mysql-connector-j를 사용하는 Java 애플리케이션에서 max-lifetime을 기본값보다 현저히 짧게 설정한 경우, mysql-connector-j 8.0.22 이상으로 업그레이드하고 -Dcom.mysql.cj.disableAbandonedConnectionCleanup=true 옵션을 추가하면 메모리 누수로 인한 OOM 현상을 방지할 수 있다. 특히 DB failover를 빠르게 처리해야 하는 환경에서는 이 옵션이 필수적이다.