메모리가 새는 줄 알았는데 GC 설정이었다
운영 서버 메모리가 배포 직후 380MB에서 시작해 4일 뒤 1.6GB까지 올라갔습니다. 재시작하면 다시 380MB로 떨어졌고요. 전형적인 누수처럼 보였습니다.
1. 힙 스냅샷부터
--heapsnapshot-signal 을 붙여 두고 하루 간격으로 세 번 떴습니다.
스냅샷 사이 Retained Size 를 비교하면 뭐가 안 없어지는지 바로 보입니다.
# 시그널로 힙 스냅샷을 뜬다. 재시작 없이 뜰 수 있다 node --heapsnapshot-signal=SIGUSR2 \ --max-old-space-size=1024 \ dist/main.js # 필요할 때 시그널만 보내면 된다 kill -SIGUSR2 $(pgrep -f dist/main.js)
그런데 세 스냅샷의 Retained Size 합계가 거의 그대로였습니다.
객체는 안 늘고 있는데 프로세스 RSS만 올라간 겁니다.
2. 그럼 뭐가 늘어난 건가
process.memoryUsage()의heapUsed는 평평rss만 계속 상승external도 소폭 상승 — 버퍼 쪽 의심
setInterval(() => { const m = process.memoryUsage() logger.info({ rss: mb(m.rss), heapUsed: mb(m.heapUsed), external: mb(m.external), }, 'mem') }, 30_000)
RSS 가 오른다고 전부 누수는 아닙니다.
heapUsed 가 평평한데 rss 만 오르면 GC 가 메모리를 OS 에
안 돌려주고 있는 것일 수 있습니다. 컨테이너 메모리 한도가 넉넉하면 V8 은 굳이 반환하지 않습니다.
3. 원인
컨테이너 메모리 한도는 2GB인데 --max-old-space-size 를 안 줬습니다.
V8 이 한도를 넉넉하다고 보고 힙을 계속 키운 겁니다. 누수가 아니라 설정 문제였습니다.
한도를 컨테이너의 75%인 1536 으로 고정하고 재배포했더니
RSS 가 4일 내내 620MB 언저리에서 평평해졌습니다.
