대규모 메시징 서비스에서 콘텐츠 생성 시 발생하는 10MB 단위의 거대 객체는 JVM 메모리 관리의 가장 큰 적입니다.
일반적인 문자나 카카오톡 메시지가 수백 바이트 단위인 것과 달리, 이례적으로 큰 데이터를 병렬 처리하며 겪은 메모리 부족(OOM) 현상과 GC 지연 문제를 어떻게 해결했는지 그 과정을 작성했습니다.
기존 Java 1.8 환경에서 Parallel GC를 사용하던 중, 대량의 이메일 발송 시 메모리 사용량이 급증하며 시스템이 멈추는 현상이 발생했습니다.
단순한 해결방법으로 G1GC로 전환했음에도 불구하고 성능 저하는 여전했습니다.
원인 분석: G1GC의 Region 메커니즘 G1GC는 전체 힙을 영역(Region)이라는 단위로 나누어 관리합니다. 이때 객체의 크기가 영역(Region) 크기의 50%를 초과하면 이를 거대객체(Humongous Object)로 간주합니다.
문제점
거대객체는 연속된 영역(Region)을 점유해야 하며, 할당과 해제 시점에 JVM에 큰 부담을 줍니다.
이는 빈번한 Full GC를 유발하고 STW(Stop-The-World) 시간을 기하급수적으로 늘립니다.
실험 결과
데이터 크기가 4MB를 넘어가는 시점부터 GC 처리 속도가 누적 속도를 따라잡지 못하는 가속화 현상을 확인했습니다.
단순히 인프라를 증설하는 것은 비용 대비 효율이 낮았습니다. 우선 소스 코드 상에서 불필요한 참조를 제거하는 작업을 선행했습니다.
2-1. Scope 제한 및 Static 사용 최소화
Static 제거
클래스 단위의 static 변수는 GC 대상에서 제외되어 메모리 누수의 주범이 됩니다.
이를 메서드 단위 객체로 전환하여 작업 완료 후 즉시 GC 대상이 되도록 스코프를 좁혔습니다.
Deep Clear 전략
List<Object>와 같은 대규모 참조 데이터 사용 시, 작업이 끝난 객체는 명시적으로 참조를 끊고(null 대입) 내부 데이터를 초기화하여 GC가 더 빠르게 메모리를 회수할 수 있도록 유도했습니다.
최적의 Region Size 도출
G1GC의 기본 설정만으로는 10MB 객체를 효율적으로 담을 수 없었습니다. 테스트 데이터를 2배수로 늘려가며(1MB~32MB) 10만 건의 시뮬레이션을 진행한 결과, 다음과 같은 최적화 수치를 찾아 반영했습니다.
주요 설정 변경
G1HeapRegionSize 조정: 10MB 객체가 Humongous 영역으로 인식되어 파편화를 일으키지 않도록, 전체 힙 크기와 연동하여 Region 사이즈를 최적화했습니다.
(예: 32MB로 설정 시 16MB 이하 객체는 일반 Region에 수용 가능)
Backpressure(압박 제어) 로직 추가: 로직 내에 Memory Threshold 체크 기능을 도입했습니다. 전체 Heap 사용량이 80%를 초과하거나, 현재 복사된 객체 덩어리가 임계치를 넘을 경우 작업에 의도적인 지연(Delay)을 주어 GC가 메모리를 확보할 시간을 벌어주었습니다.
결과: 기존 20~30%에 달하던 발송 실패율이 1% 미만으로 급감하였고, 90%를 상회하던 메모리 점유율이 20~30%대로 안정화되었습니다.
이번 장애 대응을 통해 JVM 버전이 가지는 리소스 처리 한계를 명확히 실감할 수 있었습니다.
Java 17+ 및 ZGC의 기대 효과 만약 Java 17 이상의 환경에서 ZGC를 사용할 수 있었다면 상황은 훨씬 수월했을 것입니다.
가변 페이지(ZPages): 고정된 Region 크기에 얽매이지 않고 객체 크기에 맞는 메모리를 할당하므로 거대 객체 파편화 이슈에서 자유롭습니다.
GraalVM 활용: GraalVM의 최적화된 JIT 컴파일러와 Native Image 기술은 가변 메모리 사용 효율을 높이고 Downtime을 획기적으로 줄여줍니다.
결론:
비단 로직의 효율성뿐만 아니라, 사용 중인 JVM의 버전과 옵션이 처리 가능한 리소스의 Limit을 결정한다는 점을 배울 수 있었던 작업이였습니다.
특히 64비트 시스템에서 메모리를 무작정 늘릴 경우 임계점(Compressed OOPs, 약 32GB)을 넘어가며 오히려 성능이 급락할 수 있다는 점을 항상 유의해야 합니다.
앞으로도 지속적인 버전 팔로우업과 GC 로그 분석을 통해 인프라와 소프트웨어가 조화를 이루는 최적화 지점을 찾는 노력이 필요할 것 같습니다.