Files
2nd/01_Archive/2026-04-20/Node.js 프로덕션 메모리 누수 진단.md
T

4.7 KiB

Node.js 프로덕션 메모리 누수 진단

📌 Brief Summary

Node.js 프로덕션 메모리 누수는 단일 프로세스로 장기 실행되는 Node.js의 특성상 참조가 누적되어 V8 가비지 컬렉터(GC)가 메모리를 회수할 수 없게 되면서 발생합니다 [1, 2]. 정상적인 프로세스와 달리 가비지 컬렉션 이후에도 힙 메모리 사용량이 원래 수준으로 떨어지지 않고 계단식(Ratchet)으로 상승하는 패턴을 보이는 것이 주된 특징입니다 [3, 4]. 이를 진단하고 해결하려면 힙 스냅샷 비교, 힙 프로파일링, 메모리를 계속 참조하고 있는 요인(Retainer)을 추적하는 체계적인 과정이 필수적입니다 [4, 5].

📖 Core Content

누수의 원리와 증상 (Principles and Symptoms)

  • Node.js 메모리 누수는 객체가 "유실"되는 것이 아니라 코드 어딘가에서 계속 참조되고 있어 GC가 도달할 수 없는 객체로 식별하지 못해 발생합니다 [2, 6].
  • 정상적인 프로세스는 트래픽 발생 시 힙이 증가하고 GC 이후 기준선으로 떨어지는 '톱니바퀴(Sawtooth)' 패턴을 보이지만, 누수가 발생하면 GC 후에도 힙 사용량이 떨어지지 않는 '계단식(Ratchet)' 패턴을 나타냅니다 [3, 4].
  • 주요 증상으로는 점진적인 메모리 증가, 잦고 긴 GC 일시 정지 시간, 응답 시간 저하, 그리고 궁극적으로 OOM(Out of Memory) 충돌 현상이 있습니다 [7].

핵심 진단 도구 (Core Diagnostic Tools)

  • --inspect 및 Chrome DevTools: 서버를 --inspect 플래그로 실행하여 Chrome에 연결한 후, 메모리 패널에서 힙 스냅샷을 캡처해 스냅샷 사이에 할당된 객체를 비교 분석할 수 있습니다 [3, 8, 9].
  • heapdump: 프로덕션 환경(Chrome DevTools 접근이 어려운 경우)에서 프로그래밍 방식으로 힙 스냅샷을 기록하여 로컬로 다운로드 및 분석할 수 있게 돕습니다 [8, 10, 11].
  • --heap-prof 플래그: 외부 패키지 없이 Node.js 자체에 내장된 V8 네이티브 프로파일링을 활성화하여 함수 수준의 할당 세부 내역을 파악할 수 있습니다 [12].
  • process.memoryUsage(): RSS(Resident Set Size), heapTotal, heapUsed 값을 지속적으로 확인하여 프로그래밍 방식으로 힙의 점진적인 증가 여부를 감시할 수 있습니다 [13, 14].

일반적인 누수 발생 패턴 (Common Leak Patterns)

  • 이벤트 리스너 누적 (EventEmitter Listener Accumulation): 요청 핸들러 내에서 리스너를 추가하고 제거하지 않으면 참조가 계속 누적되며, 프로덕션 환경에서는 보통 MaxListenersExceededWarning 경고가 명확한 누수 신호로 간주됩니다 [5, 11, 15].
  • 클로저 변수 유지 (Closure Variable Retention): 비동기 체인이나 타이머 콜백 등에서 대규모 데이터(예: 전체 요청/응답 객체)를 캡처하는 클로저를 사용하여 객체 수명이 불필요하게 늘어나는 경우입니다 [15-17].
  • 무제한 캐시 증가 (Unbounded Cache Growth): 최대 크기나 제한을 두지 않은 인메모리 캐시 변수에 객체가 무한정 쌓이는 패턴입니다 [15].
  • 타이머/관찰자 및 소켓 누수: clearInterval 처리되지 않은 setInterval 콜백이나, 데이터 송수신 후 닫히지 않은 스트림/소켓이 버퍼와 네트워크 핸들을 점유하여 메모리를 해제하지 못하게 만듭니다 [17, 18].

진단 및 해결 워크플로우 (Diagnosis & Fix Workflow)

  • 모니터링을 통해 메모리의 계단식 증가 패턴(Ratchet)을 확인한 뒤 베이스라인 힙 스냅샷을 캡처합니다 [4].
  • 트래픽 부하를 유발하는 행동을 실행한 후 두 번째 스냅샷을 캡처하고 두 스냅샷을 비교합니다 [4, 19].
  • 비교 결과에서 유출된 객체를 찾은 후, 해당 객체를 유지하고 있는 리테이너(Retainer) 트리를 GC 루트까지 따라가 코드를 수정하고, 수정을 확인하기 위해 테스트를 반복합니다 [4, 20].

🔗 Knowledge Connections


Last updated: 2026-04-19