JVM heap과 page cache가 범인? 메모리 사용량 증가의 숨겨진 원인 분석

by DD
2시간 전
조회수 0

SSG.COM 프로모션 서비스에서 메모리 사용률 99%에도 OOM이 발생하지 않는 현상 분석 시작

JVM heap commit 정책Logback FILE appender로 인한 page cache 증가가 주 원인으로 밝혀짐

JVM 옵션 튜닝 및 FILE appender 제거로 메모리 사용률 60%대로 안정화 및 비용 절감 달성

Kubernetes 메모리 사용량의 함정: JVM Heap vs Page Cache

Kubernetes 환경에서 `memory.usage_in_bytes`는 단순히 JVM의 Heap RSS만을 의미하는 것이 아니라, JVM Heap RSS, Metaspace, Thread Stack 등 JVM 관련 메모리와 더불어 Linux의 Page Cache까지 포함하는 개념이다. 프로모션 서비스의 경우, JVM Heap은 약 1GiB 수준으로 안정적이었으나, Logback FILE Appender가 생성하는 로그 파일이 쌓여 약 1GiB의 Page Cache를 형성하며 전체 메모리 사용량을 limit 근처까지 끌어올렸다. 이는 JVM GC 대상이 아니므로 애플리케이션 레벨에서는 메모리 누수(Memory Leak)로 인지하기 어려운 구조였다. 따라서 `memory.usage`와 `memory.working_set` 지표를 함께 분석하는 것이 중요하며, 특히 파일 기반 로깅(File-based Logging)의 영향을 간과해서는 안 된다.

JVM G1 GC의 Heap Commit 정책과 Uncommit의 어려움

JDK 11 G1 GC는 Heap 크기 조정 및 반환(Uncommit) 타이밍이 제한적이라는 특징을 가진다. 프로모션 서비스의 경우, 트래픽 증가 후 Live Heap은 줄어들었지만, JVM은 OS에 예약된 Heap 영역(Committed Heap)을 즉시 반환하지 않고 유지하는 경향을 보였다. 이는 Full GC나 Concurrent Cycle 종료 시점에 Uncommit을 검토하기 때문이며, 특히 프로모션 서비스처럼 지속적인 할당이 발생하는 환경에서는 비어있는 Region을 OS에 반환할 기회가 적었다. JEP 346(Periodic GC)이 JDK 12부터 도입된 점을 고려하면, JDK 11 환경에서는 `InitialRAMPercentage`와 `MaxRAMPercentage` 설정을 통해 Heap 상한 자체를 낮추는 것이 효과적인 해결책이었다. 이 설정은 컨테이너 limit 대비 JVM Heap의 최대 사용 비율을 제한하여, OS에 예약되는 메모리 양을 줄이는 역할을 한다.

Logback FILE Appender가 Page Cache에 미치는 영향

Linux 환경에서 애플리케이션이 파일에 로그를 기록할 때, 해당 내용은 RAM의 Page Cache에 적재된다. 프로모션 계산 서비스는 하루 수십~수백 개의 로그 파일을 생성할 정도로 로그 발생량이 많았으며, 이로 인해 Page Cache가 점진적으로 증가하여 약 1GiB까지 쌓였다. 이 Page Cache는 JVM Heap 영역과는 별개로 관리되므로, JVM GC로는 해제되지 않으며 컨테이너의 `memory.usage`에 포함된다. 해당 서비스에서는 Fluentd 기반의 콘솔 로깅(Console Logging)으로 로그 전송이 대체 가능했기 때문에, 불필요한 FILE Appender 설정을 제거하는 것으로 Page Cache 증가 문제를 해결할 수 있었다. 이는 애플리케이션 외부 요인이 메모리 사용량에 미치는 영향을 보여주는 사례이다.

메모리 사용량 증가 문제 해결을 위한 단계적 튜닝 전략

문제 해결을 위해 두 단계의 튜닝을 진행했다. 1차 변경에서는 JVM 옵션(`InitialRAMPercentage=40`, `MaxRAMPercentage=60`)을 조정하고 메모리 limit을 3G로 낮췄다. 이로 인해 Heap Committed는 즉시 개선되었으나, Page Cache 증가로 인해 사용률이 다시 상승했다. 2차 변경에서는 FILE Appender를 제거하고 메모리 limit을 3.5G로 소폭 상향 조정했다. FILE Appender 제거 직후 Page Cache는 5MiB 수준으로 급감했으며, 이후 메모리 사용률은 60%대 고원(Plateau)을 유지하며 안정화되었다. 이 과정에서 `memory.usage`와 `memory.working_set` 간의 차이를 분석하는 것이 문제의 본질을 파악하는 데 결정적이었다.

운영 환경에서 주목해야 할 메모리 관련 메트릭

서비스 운영 중 메모리 이슈를 진단하기 위해 다음과 같은 메트릭들을 종합적으로 확인해야 한다. 컨테이너 레벨에서는 `memory.limit_in_bytes`, `memory.usage_in_bytes`, 그리고 `memory.stat`의 `cache`, `rss`, `total_inactive_file` 값을 주기적으로 확인하여 JVM Heap과 Page Cache의 비율을 파악해야 한다. Datadog과 같은 모니터링 도구에서는 `memory.usage / memory.limits`, `memory.working_set / memory.limits` 비율을 통해 실제 사용 중인 메모리(Working Set)와 전체 사용량의 차이를 시각화하고, `Max Heap Committed` 및 `Heap Remaining After Full GC` 지표를 통해 JVM의 Heap 관리 상태를 추적하는 것이 중요하다. 이러한 다각적인 분석은 근본적인 원인 진단에 필수적이다.

돌아오지 않는 메모리를 찾아서