10 views
Browser engineers talk about detached DOM trees; Node engineers get the same disease as Map/Set/closure graphs that outlive every request. RSS charts tell you something is wrong; heap snapshots tell you which retainer still points at yesterday’s traffic. The unfair advantage is a disciplined method: reproduce with a stable load, capture two snapshots, diff retained size by constructor, then fix with bounded caches, WeakRef/FinalizationRegistry only where semantics allow, and tests that fail on growth.
⚡ TL;DR: Diff heap snapshots under repeatable load; hunt growing(string),Object,Map,Closureretainers; replace unboundedMapcaches with LRU + TTL; never store sockets/req objects in module-global sets. Pair with Node Heap Snapshots in Production and AsyncLocalStorage Overhead.
Node Memory Leaks: production guidance
typescript
// scripts/leak-repro.ts
import { writeHeapSnapshot } from "node:v8";
import { app } from "../src/app.js";
async function burst() {
for (let i = 0; i < 5_000; i++) {
await app.inject({ method: "GET", url: `/items/${i % 200}` });
}
global.gc?.();
}
await burst();
writeHeapSnapshot("/tmp/before.heapsnapshot");
await burst();
await burst();
global.gc?.();
writeHeapSnapshot("/tmp/after.heapsnapshot");
// Diff in Chrome DevTools → Comparison → Rank by Retained Size delta
typescript
// ❌ Debugging only with process.memoryUsage().rss in Grafana
// RSS moves for many reasons — allocator caches, fragmentation — not just leaks
The usual Node retainers
typescript
// src/bad-cache.ts
const cache = new Map<string, object>(); // ❌ unbounded
export function getUser(id: string) {
if (!cache.has(id)) cache.set(id, loadUser(id));
return cache.get(id);
}
// src/good-cache.ts
import { LRUCache } from "lru-cache";
const cache = new LRUCache<string, object>({
max: 5_000,
ttl: 60_000,
// ✅ updateAgeOnGet only if product wants sliding TTL
});
typescript
// EventEmitter / ALS traps
const sockets = new Set<Socket>();
server.on("connection", (s) => {
sockets.add(s);
// ❌ missing sockets.delete on close → classic growth
s.on("close", () => sockets.delete(s)); // ✅
});
WeakRef is not a free lunch
typescript
// Use WeakRef only when values may disappear and you can recompute
const weakUsers = new Map<string, WeakRef<object>>();
const registry = new FinalizationRegistry((id: string) => weakUsers.delete(id));
export function remember(id: string, obj: object) {
weakUsers.set(id, new WeakRef(obj));
registry.register(obj, id);
}
// ✅ Still bound the Map of WeakRefs by key count — WeakRef doesn't shrink the key set
Closing checklist
✅ Dos – ✅ Diff two snapshots under identical load with--expose-gc
– ✅ Bound every process-global cache (max + TTL)
– ✅ Remove listeners/sockets from module-level collections on close
– ✅ Add a CI soak that fails on retained-size growth for suspect caches
– ✅ Capture production snaps only via the gated path
❌ Don’ts
– ❌ Don’t trust RSS alone to name the leak
– ❌ Don’t use unbounded Map keyed by user/request IDs
– ❌ Don’t expect WeakRef to fix a Map that retains keys forever
– ❌ Don’t keep req/res in closures scheduled past the request lifetime
Related reading
- Node Heap Snapshots in Production: Safe Capture Under Incident Load
- AsyncLocalStorage Overhead: Measure Before Wrapping Every Request Path
- Event Loop Blockers: Perfetto Traces That Metrics Alone Will Miss
- Node Flamegraphs on ECS: Continuous Profiling That Survives Scale
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- Css Animation Performance: Performance-Optimized Motion for Modern UIs
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
