Files
2nd/01_Archive/2026-04-20/Node.js 메모리 최적화.md
T

5.5 KiB

Node.js 메모리 최적화

📌 Brief Summary

Node.js는 V8 엔진을 기반으로 실행되는 단일 프로세스이므로, 시간이 지남에 따라 메모리 누수가 지속적으로 누적될 수 있어 효율적인 메모리 관리가 필수적입니다 [1]. 정상적인 상태의 힙 메모리 사용량은 가비지 컬렉션(GC) 이후 원래 수준으로 돌아가는 톱니바퀴(sawtooth) 패턴을 보이지만, 메모리 누수가 발생하면 반환되지 않고 지속적으로 상승하는 래칫(ratchet) 패턴을 그립니다 [2]. 메모리 최적화는 각종 힙 프로파일링 도구와 명령줄 플래그를 활용하여 애플리케이션의 누수 패턴을 찾아 해결하고, GC 설정 및 힙 공간 크기를 튜닝하여 시스템의 안정성과 성능을 극대화하는 과정입니다 [2-4].

📖 Core Content

V8 메모리 구조 및 가비지 컬렉션(GC)

  • Node.js의 V8 엔진은 메모리를 힙(Heap)과 스택(Stack)으로 나누어 관리합니다 [5]. 스택은 지역 변수 및 함수 호출 프레임을 후입선출(LIFO) 원칙에 따라 관리하며, 힙은 동적으로 생성된 자바스크립트 객체와 데이터가 저장되는 곳으로 가비지 컬렉터의 주요 관리 대상이 됩니다 [6, 7].
  • 세대별 가설(Generational hypothesis)에 기반하여 힙은 '새로운 공간(New Space)'과 '오래된 공간(Old Space)'으로 나뉩니다 [5]. New Space에서는 단기 객체가 할당되며, 가볍고 빠른 마이너 GC(Scavenger)가 자주 실행되어 사용되지 않는 메모리를 회수합니다 [5, 8].
  • New Space에서 여러 번의 GC 주기를 생존한 객체들은 장기 보존 데이터로 간주되어 Old Space로 승격(Promotion)되며, 이 영역은 무겁지만 덜 빈번하게 실행되는 메이저 GC(Mark-Sweep-Compact 알고리즘)를 통해 관리됩니다 [5, 9, 10].

메모리 누수 감지 및 모니터링

  • process.memoryUsage()를 사용하면 RSS(Resident Set Size), heapTotal, heapUsed 등의 수치를 통해 실행 중인 프로세스의 메모리 상태를 파악할 수 있습니다 [11].
  • 상용 환경에서는 prom-client를 통해 메모리 메트릭을 추출하고 Grafana와 같은 도구로 경고 규칙(Alert rule)을 설정하여, 메모리 부족(OOM) 크래시가 발생하기 전 누수를 조기 감지하는 것이 좋습니다 [12].
  • 누수가 의심될 때는 --inspect 플래그를 통해 Chrome DevTools에 연결하여 객체 할당 타임라인을 기록하거나, heapdump 라이브러리 및 --heap-prof 플래그를 활용해 힙 스냅샷을 캡처하여 추적할 수 있습니다 [2, 12, 13]. 트래픽 발생 전후의 두 가지 힙 스냅샷을 비교하면 반환되지 않고 남아 있는 메모리 할당 객체들을 정확히 찾아낼 수 있습니다 [13].

자주 발생하는 메모리 누수 원인과 해결 패턴

  • 이벤트 리스너 누적: on('event', fn) 호출 후 리스너를 명시적으로 제거하지 않아 발생하며, 단일 이벤트 발생기에 리스너가 10개를 초과하면 MaxListenersExceededWarning이 발생하여 누수를 강력히 암시합니다 [14, 15].
  • 클로저(Closure) 변수 유지: 요청 및 응답 객체 전체와 같은 커다란 변수가 클로저에 캡처된 상태로 콜백이나 비동기 체인 내에 남겨지면 가비지 컬렉터가 이를 수집하지 못합니다 [15].
  • 무제한 캐시 및 잊혀진 타이머: 인메모리 캐시를 사용할 때 한도를 설정하지 않거나, setInterval로 생성된 타이머를 clearInterval로 정리하지 않으면 연관된 클로저 전체가 메모리에 영구적으로 보존됩니다 [15, 16].
  • 종료되지 않은 스트림과 소켓: 스트림이나 네트워크 핸들의 응답 본문을 다 소비하지 않은 경우, 내부 버퍼가 유지되므로 반드시 cancel()이나 destroy()를 호출해 정리해야 합니다 [16].

메모리 튜닝용 명령줄 플래그(CLI Flags)

  • --max-old-space-size: Old Space의 한계를 메가바이트(MB) 단위로 설정하며, 대량의 데이터 배치 처리나 많은 사용자 세션 등 메모리 소모가 큰 애플리케이션의 성능 저하 및 OOM 방지에 사용됩니다 [4].
  • --max-semi-space-size: New Space의 크기를 조절하며, 단기 객체(예: API 요청마다 생성되는 임시 객체)가 대량으로 생성되는 환경에서 늘려주면 잦은 마이너 GC 실행을 줄여 전반적인 성능을 높일 수 있습니다 [17].
  • --gc-interval--expose-gc: 가비지 컬렉션의 빈도를 강제로 조정하거나, 프로그램 내부에서 global.gc()를 호출해 수동으로 가비지 컬렉션을 실행할 수 있도록 하는 옵션입니다 [18-20].

🔗 Knowledge Connections

  • Related Topics: V8 JavaScript Engine, Garbage Collection (GC), Heap Snapshot
  • Projects/Contexts: Chrome DevTools Memory Profiling, Node.js Production Environments
  • Contradictions/Notes: --expose-gc 플래그를 통한 수동 가비지 컬렉션 호출(global.gc())은 대량의 데이터 처리 후 즉시 메모리를 회수해야 하는 특수 상황에서 유용할 수 있지만, 일반적인 V8의 자동 GC 메커니즘을 대체하는 것은 아니며 남용 시 과도한 GC 사이클 실행으로 인해 애플리케이션 성능을 크게 저하시킬 위험이 있습니다 [20].

Last updated: 2026-04-19