Node.js Performance: Profiling and Fixing Memory Leaks
Memory leaks in Node.js are insidious. The process memory grows slowly until it crashes, usually at 3am.
Common Causes
- Global variables accumulating data — caches without eviction
- Event listeners not removed — registering without cleanup
- Closures holding references — callbacks capturing scope
- Timers not cleared — setInterval never cancelled
Diagnosing With --inspect
node --inspect dist/server.js
# Open Chrome DevTools → chrome://inspect → Memory tab
# Take heap snapshots before/after load to find growing objects
The Most Common Leak: Unbounded Cache
// Leaking: grows forever
const cache = new Map<string, Data>();
function getData(key: string) {
if (!cache.has(key)) cache.set(key, fetchData(key));
return cache.get(key);
}
// Fixed: bounded with LRU eviction
import LRU from 'lru-cache';
const cache = new LRU<string, Data>({ max: 500, ttl: 1000 * 60 * 5 });
Event Listener Cleanup
// Leaking: new listener added on every request
app.get('/stream', (req, res) => {
process.on('SIGTERM', () => res.end()); // Never cleaned up!
});
// Fixed: cleanup on connection close
app.get('/stream', (req, res) => {
const cleanup = () => res.end();
process.on('SIGTERM', cleanup);
req.on('close', () => process.off('SIGTERM', cleanup));
});
Memory Monitoring in Production
setInterval(() => {
const mem = process.memoryUsage();
metrics.gauge('memory.heap_used', mem.heapUsed);
metrics.gauge('memory.heap_total', mem.heapTotal);
metrics.gauge('memory.rss', mem.rss);
// Alert if heap is growing consistently
if (mem.heapUsed / mem.heapTotal > 0.85) {
alerts.warn('High heap usage', { usage: mem.heapUsed });
}
}, 30000);
Automatic Heap Dumps on OOM
node --max-old-space-size=512 \
--heapsnapshot-signal=SIGUSR2 \
dist/server.js
# Send SIGUSR2 to get a heap snapshot without crashing
Production monitoring, memory alerting, and observability tooling are baked into the AI SaaS Starter Kit — ship with visibility from day one.
Top comments (0)