Node.js 메모리 누수, 256KB의 비밀을 파헤치다!

by DD
2개월 전
조회수 28

Node.js 애플리케이션에서 메모리 사용량 급증(Memory Usage Spike) 현상 발생, 특정 Pod에서 400MiB 이상의 메모리 사용

V8 힙(V8 Heap) 분석 결과, V8 힙 자체의 문제는 아니었으며, 256KB 크기의 다수 메모리 맵핑(Memory Mapping) 발견

glibc 메모리 할당(Memory Allocation) 문제와 V8의 가비지 컬렉션(Garbage Collection) 미흡이 원인으로 지목됨

`--max-old-space-size` 및 `MALLOC_ARENA_MAX` 설정을 통해 메모리 사용량 감소(Memory Usage Reduction) 및 안정성 확보

메모리 사용량 급증의 원인 분석

문제의 근본 원인을 파악하기 위해, 저자는 Node.js 프로세스 내부의 메모리 사용량 분석을 시도했다. 특히, `smaps_rollup`을 통해 메모리 사용량을 세분화하여 분석한 결과, Private Dirty Anonymous 메모리(Private Dirty Anonymous Memory) 영역에서 과도한 메모리 사용이 확인되었다. 이는 V8 힙(V8 Heap)이 아닌, 네이티브(Native) 영역에서 할당된 메모리가 해제되지 않고 누적되는 현상을 의미한다. 이러한 분석은 문제 해결의 핵심 단서를 제공했다.

V8 힙(Heap)과 가비지 컬렉션(Garbage Collection)의 역할

저자는 V8 Inspector를 활용하여 힙(Heap) 메모리 사용량을 확인했지만, V8 힙 자체에는 문제가 없음을 확인했다. 하지만, 실제 사용 중인 메모리(RSS)와 V8이 관리하는 메모리(heapUsed) 간의 차이가 컸다. 이는 V8이 할당한 메모리를 가비지 컬렉션(Garbage Collection)하지 않아, 사용하지 않는 메모리가 계속 남아있는 문제를 시사한다. 즉, V8의 가비지 컬렉션(Garbage Collection) 최적화 부족이 메모리 누수의 주요 원인 중 하나로 작용했다.

glibc 메모리 할당(Memory Allocation) 문제와 해결

분석 결과, glibc의 메모리 아레나(Memory Arena) 단편화(Fragmentation) 문제 또한 메모리 누수에 기여하는 것으로 나타났다. 특히, 256KB 크기의 메모리 맵핑(Memory Mapping)이 다수 생성되는 현상을 발견했다. 저자는 `MALLOC_ARENA_MAX` 설정을 통해 glibc의 아레나 수를 제한하여, 메모리 단편화(Memory Fragmentation)를 완화하고자 했다. 이는 네이티브(Native) 메모리 할당의 효율성을 높이는 데 기여했다.

최적화 설정 및 성능 개선

저자는 `--max-old-space-size` 옵션을 사용하여 V8의 Old Generation 힙(Heap) 크기를 제한하고, `MALLOC_ARENA_MAX` 설정을 통해 glibc의 아레나 수를 제한하는 방식으로 문제를 해결했다. 이러한 설정을 통해 V8의 가비지 컬렉션(Garbage Collection) 빈도를 증가시키고, glibc의 메모리 단편화(Memory Fragmentation)를 감소시켜, 궁극적으로 메모리 사용량을 줄이고 애플리케이션의 안정성을 확보했다. 이는 Node.js 애플리케이션의 성능 최적화에 대한 실질적인 가이드를 제시한다.

커뮤니티의 추가적인 제안

댓글에서는 glibc 대신 jemalloc을 사용하는 방안이 제시되었다. jemalloc은 glibc보다 메모리 단편화(Memory Fragmentation) 문제를 개선할 수 있으며, 특히 멀티스레드(Multithreaded) 환경에서 성능 향상을 기대할 수 있다. 또한, 메모리 아레나(Memory Arena) 수를 늘려 메모리 할당 효율을 높이는 방법도 제안되었다. 이러한 커뮤니티의 제안은 문제 해결을 위한 다양한 접근 방식을 보여준다.

Where did 400 MiB go?