#Backend

의심했던 범인은 알리바이가 있었다.

의심했던 범인은 알리바이가 있었다.
01

Summary

AWS가 범인인 줄 알았는데 알리바이가? JFR로 검거한 12분짜리 배치 행(Hang)의 진실

코드 한 줄 바꾸지 않고 프로덕션 미스터리를 명쾌하게 해결한 무신사 WMS팀의 실전 디버깅기

무신사 WMS팀이 간헐적인 Jenkins 배치 지연 현상을 해결하기 위해 Java Flight Recorder(JFR)를 실무에 전격 투입한 여정을 담은 아티클입니다. 단순 정황 증거와 부정확한 로그 분석에 의존해 엉뚱한 라이브러리를 고칠 뻔한 위기를 극복하고, 저오버헤드 진단 툴을 통해 팩트 기반의 분석을 수행한 과정을 보여줍니다. 결국 디바이드 앤 컨커 방식으로 진범인 N+1 쿼리를 찾아내고, 통합 쿼리 처리를 통해 인프라 부하를 8분의 1로 줄인 짜릿한 최적화 경험을 공유합니다.

  • 01프로덕션 환경에 코드 수정 없이 안전하게 JVM 옵션만으로 JFR을 상시 적용하고 산출물을 자동 아카이빙하는 빌드 파이프라인 구성법 제시
  • 02JDK Mission Control(JMC)의 세 가지 핵심 패널인 Threads, Socket I/O, Method Profiling을 조합하여 시스템 병목을 체계적으로 역추적하는 프레임워크 제공
  • 03기동 로그의 특정 에러 출력 지점과 행 발생 시점이 일치하는 정황적 증거의 함정을 JFR 데이터 분석으로 완벽히 반박한 검증 과정
  • 04동일 쿼리가 한산한 시간대(7ms)와 배치 대량 수행 시간대(1.6s)에 따라 200배 이상의 속도 차이를 보이는 DB 자원 경합 원인 규명
  • 05단건 루프 조회를 결합도 낮은 대형 통합 조회로 전환하고 복잡한 비즈니스 정렬 조건을 Java 인메모리 연산으로 흡수하는 리팩토링 사례

RECOMMENDATION

안전성이 극도로 중요한 프로덕션 환경에서 원인 불명의 대기 상태나 병목을 마주해 곤경에 처한 자바/스프링 백엔드 시니어 개발자에게 강력히 권장합니다. 특히 상시 켜두어도 성능 저하가 없는 실시간 진단 도구를 통해 추측성 튜닝의 반복을 멈추고 고도로 효율적인 팩트 중심의 디버깅을 지향하는 엔지니어에게 훌륭한 실무 교과서가 될 것입니다.

The Problem

Jenkins로 기동하는 Spring Batch 애플리케이션에서 Spring 기동 이후 첫 Step이 시작되기 전까지 간헐적으로 5분에서 12분 동안 일시 정지(Hang)되는 현상이 발생했습니다. 기존의 스레드 덤프나 힙 덤프 방식은 분석가가 행 발생 시점에 대기해야 하고 일회성 스냅샷만 제공하여 실시간 시간 흐름에 따른 병목 추적이 불가능했습니다.

The Solution

운영 중인 코드의 수정 없이 JVM 기동 옵션만으로 동작하는 저오버헤드 프로파일러인 Java Flight Recorder(JFR)를 적용하여 빌드 종료 시 파일을 수집했습니다. 이후 JDK Mission Control(JMC)로 분석을 진행하여 기존 용의선상에 있던 AWS 로깅 라이브러리의 무죄를 입증하고, Aurora RO 복제본 DB에 파티션 단위로 루프를 돌며 조회를 수행하던 N+1 쿼리 병목을 식별하여 통합 그룹 쿼리 방식으로 수정했습니다.

The Result

N+1 조회를 제거하여 Aurora RO 복제본에 대한 소켓 read 횟수가 138회에서 32회로 급감했고, 대형 배치 기준 조회 성능이 최대 14.5배 향상되었습니다. 쿼리량 감소에 따라 동시 배치 실행 시 DB CPU 및 I/O 경합 부하가 8분의 1 수준으로 줄어들어 간헐적으로 발생하던 12분짜리 행 현상이 근본적으로 해결되었습니다.

Trade-off

개별 파티션 필터를 제거하고 대용량의 그룹 데이터를 한 번에 통합 조회하게 됨에 따라, 메모리 내부에서 그룹화 및 지그재그 정렬 처리를 수행하기 위한 Java CPU 연산과 메모리 사용량이 다소 증가했습니다. 또한 원본 DB의 일시적인 성능 한계로 인한 병목 현상은 단일 쿼리 최적화만으로는 근본적 해결이 불가능하여 중장기적으로 Temporal 워크플로우 엔진 이관을 통한 구조 개선이 요구됩니다.

03

Key Concepts

Concept · 01

Java Flight Recorder (JFR)

OpenJDK 런타임에 내장되어 1~2% 수준의 매우 낮은 오버헤드로 JVM 내부에서 일어나는 광범위한 진단 정보(스레드, GC, 락, I/O 등)를 지속적으로 수집할 수 있는 고성능 프로파일러입니다.

  • 애플리케이션 소스 코드 변경 없이 오직 한 줄의 JVM 스타트 옵션 설정을 사용하여 상시 구동되도록 설정했습니다.
  • 배치가 정상 혹은 비정상 종료되는 시점과 상관없이 디바이스에 바이너리 파일 형태로 자동 덤프되도록 구성했습니다.
Concept · 02

JDK Mission Control (JMC)

JFR이 생성한 바이너리 데이터 분석 파일을 시각화하여 특정 시점의 상세 리소스 소비 현황과 코드 흐름을 프로파일링해 주는 인터랙티브 분석 도구입니다.

  • Threads 패널을 활용해 배치의 main 스레드가 작동 시간의 80% 이상을 소켓 대기 상태로 보냈음을 한눈에 입증했습니다.
  • Socket I/O 패널의 remote 주소 그룹화 기능을 활용해 실제 지연이 AWS가 아닌 Aurora RO 복제본 조회 과정에서 집중되었음을 정량적으로 추려냈습니다.
Concept · 03

N+1 Query Problem

연관 데이터를 조회할 때 한 번의 상위 조회 쿼리 이후 루프 내에서 하위 엔티티들을 건건이 추가로 쿼리하여 원치 않는 대규모의 데이터베이스 요청 왕복(Round-Trip)이 수반되는 비효율적 패턴입니다.

  • BatchPreloaderService의 파티션 순회 과정에서 단일 대상 쿼리 호출이 N번 루프로 실행되는 전형적인 성능 저하 상태를 포착했습니다.
  • 개별 쿼리 필터를 전면 걷어내고 한 번에 상위 데이터 리스트를 통째로 가져와 메모리 맵(Map)에서 매핑하는 통합 쿼리 형태로 개편했습니다.