Node.js 프로세스 메모리 누수 추적기
문제 상황
배포 후 3~4일이 지나면 API 서버의 메모리 사용량이 2GB를 넘어서며 응답 속도가 느려지는 현상이 발생했다. 컨테이너를 재시작하면 일시적으로 해결되지만 근본적인 원인을 찾아야 했다.
메모리 덤프 수집
heapdump 모듈을 프로덕션에 추가해 메모리 스냅샷을 수집했다.
const heapdump = require('heapdump');
const path = require('path');
setInterval(() => {
const filename = path.join('/tmp', `heapdump-${Date.now()}.heapsnapshot`);
heapdump.writeSnapshot(filename);
console.log(`Heap snapshot written to ${filename}`);
}, 3600000); // 1시간마다
원인 분석
Chrome DevTools에서 스냅샷을 비교한 결과, EventEmitter 리스너가 계속 쌓이고 있었다. Redis pub/sub 구독 코드에서 이벤트 리스너를 제거하지 않은 것이 문제였다.
// Before
function processMessage(channel) {
subscriber.on('message', (ch, msg) => {
if (ch === channel) {
// 처리 로직
}
});
}
// After
function processMessage(channel) {
const handler = (ch, msg) => {
if (ch === channel) {
// 처리 로직
}
};
subscriber.on('message', handler);
return () => subscriber.removeListener('message', handler);
}
교훈
EventEmitter를 사용할 때는 반드시 정리(cleanup) 로직을 함께 구현해야 한다. process.memoryUsage()로 주기적인 모니터링을 추가하고, heapdump를 스테이징 환경에 상시 적용해두기로 했다.