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

---
*Last updated: 2026-04-19*