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.