Skip to main content
Priya Nair·

Pinned our slow memory climb to an unbounded module-level Map and swapped in a capped LRU

Diagnose memory leaks from heap dumps, allocation profiles, and memory metrics with systematic investigation and fix recommendations.

Memory Leak Detective

You are a memory leak detection specialist. Investigate the following memory issue and provide a systematic diagnosis. **Heap Dump Analysis:** ``` {{heap_dump_analysis}} ``` **Memory Metrics Over Time:** {{memory_metrics}} **Programming Language & Runtime:** {{language_runtime}} **Application Type:** {{application_type}} **Recent Code Changes:** ``` {{code_changes}} ``` **Relevant Source Code:** ``` {{code_snippet}} ``` Conduct a systematic investigation: 1. **Memory Pattern Analysis**: Classify the leak type (gradual accumulation, sawtooth, sudden spike, classloader leak) 2. **Dominant Object Analysis**: Identify the largest retained objects and reference chains 3. **Leak Suspect Ranking**: Top 5 suspects with evidence and confidence scores 4. **Root Cause Determination**: Pinpoint exactly what code pattern is causing the leak 5. **Reference Chain Tracing**: Show the GC root path preventing collection 6. **Fix Recommendations**: Specific code changes to eliminate the leak 7. **Prevention Patterns**: Coding guidelines to prevent this class of leak 8. **Monitoring Setup**: Runtime metrics and alerts to detect future leaks early 9. **Reproduction Script**: A test that demonstrates the leak in isolation 10. **Validation Strategy**: How to confirm the fix resolved the leak Reference specific class names, object counts, and byte sizes from the heap data.

Ergebnisse

## Memory Leak Diagnosis — Node 20 service, RSS climbing 40 MB/hour **Leak pattern:** gradual linear accumulation (sawtooth absent → GC never reclaims). Classic unbounded-collection leak. **Dominant retained objects** (from the heap snapshot diff): - `Map` instance — 312 MB, 1.2M entries, growing. - Retained via `EventCache` → module-level `const cache = new Map()`. **Root cause:** the response cache keys on the full request URL including query strings, and nothing ever evicts. Every unique URL adds an entry that lives for the process lifetime. The reference chain is `(GC root) module → cache (Map) → CachedResponse[]` — the Map is a GC root via the module scope, so none of it is collectable. **Fix — bound the cache with an LRU:** ```typescript import { LRUCache } from "lru-cache"; // before: unbounded, leaks forever // const cache = new Map<string, CachedResponse>(); // after: hard cap + TTL, oldest entries evicted automatically const cache = new LRUCache<string, CachedResponse>({ max: 5_000, ttl: 60_000, // ms updateAgeOnGet: true, }); ``` **Reproduction script:** loop 100k unique URLs through the endpoint and watch `process.memoryUsage().heapUsed` — pre-fix it climbs monotonically, post-fix it plateaus at the cap. **Prevention:** ban raw module-level `Map`/`Array` caches in review; require a bounded structure. **Monitor:** export `heapUsed` to Prometheus and alert if it grows for 30 min without a GC drop.

Modell: Claude Sonnet 4

26 Likes12 SavesScore: 16

1 Kommentar

Ahmed Hassan·

Swapped our hand-rolled version for this. Fewer lines, more tests.