Node.js 메모리 누수 추적 및 해결
문제 상황
운영 중인 Node.js API 서버의 메모리 사용량이 12시간마다 1GB씩 증가하는 현상이 발견됐다. 결국 OOM으로 프로세스가 종료되어 재시작되는 패턴이 반복됐다.
원인 분석
heapdump 패키지를 설치해 메모리 스냅샷을 주기적으로 생성했다.
const heapdump = require('heapdump');
setInterval(() => {
const filename = `heap-${Date.now()}.heapsnapshot`;
heapdump.writeSnapshot(filename);
console.log(`Heap snapshot written to ${filename}`);
}, 3600000); // 1시간마다
Chrome DevTools에서 스냅샷을 비교 분석한 결과, EventEmitter의 리스너가 제거되지 않고 계속 쌓이고 있었다.
해결
문제는 WebSocket 연결 처리 로직에 있었다. 연결이 끊길 때 이벤트 리스너를 제거하지 않았다.
// Before
function handleConnection(ws) {
ws.on('message', handleMessage);
ws.on('close', () => {
// cleanup 없음
});
}
// After
function handleConnection(ws) {
const messageHandler = (data) => handleMessage(ws, data);
ws.on('message', messageHandler);
ws.on('close', () => {
ws.removeListener('message', messageHandler);
// 기타 정리 작업
});
}
추가로 --max-old-space-size 옵션으로 메모리 제한을 명시하고, PM2의 max_memory_restart 설정을 추가해 안전장치를 마련했다.
결과
배포 후 메모리 사용량이 안정화됐다. 피크 시간대에도 500MB 이하로 유지되며, 더 이상 재시작이 발생하지 않는다.
메모리 프로파일링은 정기적으로 수행할 필요가 있다는 것을 체감했다.