← All posts
Debugging a Memory Leak in Production Node.js
RSS climbed 40MB an hour until the pod OOM'd. A heap snapshot and one closure later, we found it.
The graph was a perfect ramp: memory up and to the right until Kubernetes killed the pod and the cycle restarted.
Catch it in the act
# Grab a heap snapshot from a running process
kill -USR2 <pid> # with --heapsnapshot-signal=SIGUSR2Comparing two snapshots an hour apart, one retained set kept growing: an event listener added per request and never removed. A closure captured the request object, so every request leaked its whole context.
// The leak: a listener added every request, never cleaned up
emitter.on("tick", () => process(req)); // req is captured foreverThe fix was once plus an explicit off on teardown. RSS went flat.
Leaks are rarely exotic. They're almost always something added in a loop and never removed.
More to read