#Backend

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

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

Summary

99% 치솟은 쿠버네티스 메모리, 왜 OOM은 나지 않았을까?

코드가 아닌 컨테이너 밖에서 물이 새고 있었다! JVM 힙 메모리 옵션과 Page Cache의 숨겨진 비밀

쿠버네티스 환경에서 Spring Boot 애플리케이션을 운영하는 엔지니어라면 한 번쯤 마주쳤을 메모리 포화 현상의 진범을 밝힙니다. 코드 레벨의 메모리 누수가 아니라 JDK 11 G1 GC의 힙 반환 한계와 Logback 파일 로깅으로 인한 Linux Page Cache 비대화라는 원인을 분석해 냅니다. 구체적인 cgroup 실측 지표 수집 기법부터 컨테이너 환경의 로그 아키텍처 최적화 전략까지 단계별 해결 가이드를 공유합니다.

  • 01Kubernetes의 메모리 측정 기준(cgroup memory.usage)에 JVM RSS뿐만 아니라 파일 I/O 기반의 Page Cache가 누적된다는 구조적 메커니즘 확인
  • 02JDK 11 G1 GC가 힙 메모리 영역을 점진적으로 OS에 적극 반환하지 못해 발생하는 Committed Heap 고원 현상 진단
  • 03대용량 로컬 파일 로깅이 운영체제 단의 캐시를 채워 memory.usage를 극한으로 밀어올리고 있던 영향력 입증
  • 04중복된 File Appender를 제거하고 가상화 환경에 부합하는 콘솔 출력 방식으로 아키텍처를 단일화하여 리소스 원천 회수
  • 05초기/최대 RAM Percentage의 합리적 재지정(40%/60%)을 통해 실사용 영역과 예약 영역 간의 불균형 최적화 완료

RECOMMENDATION

컨테이너 환경에서 JVM을 가동할 때는 힙 상한을 최대 60% 수준으로 보수적으로 정의하고, 중앙 집중식 로그 스택이 갖춰졌다면 불필요한 로컬 파일 로그 생성을 철저히 비활성화해야 메모리 병목을 피할 수 있습니다.

The Problem

Kubernetes 환경에서 운영 중인 프로모션 계산 서비스의 메모리 사용률(usage/limits)이 지속적으로 99%에 달했으나, OOM 장애나 Pod 재시작 현상이 발생하지 않아 원인 파악 및 최적화에 난항을 겪고 있었습니다.

The Solution

JVM 힙 메모리 예약 정책(Initial/MaxRAMPercentage)을 하향 조정하고, Fluent-bit 기반 통합 로그 아키텍처 환경에서 중복으로 존재하던 Logback RollingFileAppender 설정을 제거하여 로컬 파일 출력을 전면 중단했습니다.

The Result

Logback을 통해 생성되던 약 1GiB 크기의 리눅스 Page Cache가 11MB 수준으로 소거되었고, 최종적으로 전체 메모리 사용률이 99%에서 60%대의 안정적인 평탄 상태로 완벽하게 안정화되었습니다.

Trade-off

Pod 로컬 내부 경로에 생성되던 파일 로그가 사라짐에 따라 컨테이너 내부 셸 접속을 통한 직접적인 로그 확인은 불가해졌으나, 중앙화된 외부 로그 분석 플랫폼으로 상시 조회가 가능해 실무적인 제약 사항은 최소화되었습니다.

03

Key Concepts

Concept · 01

cgroup memory.usage

리눅스 커널 수준에서 프로세스 그룹 전체가 점유하는 메모리 총량으로, 실제 애플리케이션의 메모리 외에 캐시 영역과 가상 메모리 매핑 데이터까지 모두 합산되어 모니터링 시스템에 수집되는 기본 규격입니다.

  • 애플리케이션 실제 메모리인 RSS에 대규모의 Logback Page Cache가 결합하여 전체 제한선의 99%에 달하게 한 요인입니다.
Concept · 02

Page Cache

운영체제가 파일에 대한 반복적 읽기/쓰기 성능을 비약적으로 끌어올리기 위해 남는 물리적 메모리에 저장하는 디스크 복사본이자 파일 버퍼 공간입니다.

  • Logback의 RollingFileAppender가 지속적으로 대량의 물리 파일 로그를 기록하면서 생성되었으며, GC의 가비지 회수와는 완벽히 무관하게 독립적으로 누적되어 동작했습니다.
Concept · 03

Committed Heap

JVM이 필요에 따라 OS 측으로부터 할당받아 즉각적인 자바 객체 생성이 가능하도록 예약해 확보해 둔 가용 메모리 영역입니다.

  • 프로모션 서비스가 가동되는 동안 가용 객체 볼륨이 최저점으로 떨어져도 G1 GC의 한계와 고정 옵션으로 인해 해당 메모리를 계속 점유하는 현상의 직접적 근거가 되었습니다.