Skip to content

Commit 6707794

Browse files
authored
fix(cache): prevent unbounded TTLCache memory exhaustion and runtime instability (JhaSourav07#548)
## Description Fixes JhaSourav07#537 This PR implements a bounded-capacity eviction strategy for the global `TTLCache` instances used inside `lib/github.ts`. Previously, the cache relied only on TTL expiration and did not enforce any maximum capacity limits. Under sustained unique-user traffic (crawler indexing, bots, traffic spikes, or large username scans), the internal `Map` continuously retained strong references for new cache entries, causing unbounded heap growth and increasing the risk of Node.js/Vercel runtime instability or OOM crashes. This fix introduces explicit cache ceilings while preserving the existing TTL lifecycle and maintaining full backward compatibility. ### Changes Implemented - Added bounded cache size limits for: - `contributionsCache` - `profileCache` - `reposCache` - Added production-focused defensive comments explaining: - memory exhaustion risks - crawler amplification scenarios - runtime stability concerns - bounded eviction rationale ### Root Cause The cache instances were initialized without supplying `maxSize`: const contributionsCache = new TTLCache<ContributionCalendar>(); const profileCache = new TTLCache<GitHubUserProfile>(); const reposCache = new TTLCache<GitHubRepo[]>(); Because the internal `Map` retained strong references to active entries: - memory scaled linearly with unique usernames - garbage collection could not reclaim active objects - long-lived processes accumulated memory continuously ### Validation Successfully verified with: - `npm run test` - `npm run lint` - `npm run build` ### Test Results - 22/22 test suites passed - 275/275 tests passed - Production build compiled successfully ### Runtime Validation Stress-tested with 50,000 unique usernames. | Metric | Result | |---|---| | Heap Before | 7.41 MB | | Heap After | 15.55 MB | | Behavior Before Fix | Unbounded growth | | Behavior After Fix | Stable bounded plateau | ### Impact This PR prevents: - unbounded heap growth - excessive GC pressure - potential Vercel OOM crashes - runtime instability under crawler/bot traffic while preserving: - existing TTL behavior - cache semantics - API compatibility - backward compatibility --- ## Pillar - [ ] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x] 🛠️ Other (Bug fix, refactoring, docs) --- ## Visual Preview N/A — backend runtime stability fix --- ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`). - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors. - [x] My commits follow the Conventional Commits format. - [x] I have started the repo. - [x] I have made sure that I have only one commit in this PR. - [x] All tests and production builds pass successfully.
2 parents 4dfca50 + 89c6332 commit 6707794

1 file changed

Lines changed: 14 additions & 3 deletions

File tree

lib/github.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,20 @@ interface GitHubUserProfile {
114114
plan?: { name?: string } | null;
115115
}
116116

117-
const contributionsCache = new TTLCache<ContributionCalendar>();
118-
const profileCache = new TTLCache<GitHubUserProfile>();
119-
const reposCache = new TTLCache<GitHubRepo[]>();
117+
// Named constants to avoid magic numbers and allow future tuning
118+
const MAX_CONTRIBUTIONS_CACHE_SIZE = 1000;
119+
const MAX_PROFILE_CACHE_SIZE = 1000;
120+
const MAX_REPOS_CACHE_SIZE = 500;
121+
122+
// Bounded capacity controls to prevent unbounded heap memory leaks (OOM).
123+
// Under continuous crawler/bot scanning or viral peaks, unbounded cache size
124+
// allocations will exhaust Node/Vercel serverless RAM.
125+
// Specifying explicit capacity limits enforces a First-In, First-Out (FIFO)
126+
// eviction strategy (since standard ES6 Map maintains key insertion order) and
127+
// bounds max memory consumption to stable, predictable boundaries.
128+
const contributionsCache = new TTLCache<ContributionCalendar>(MAX_CONTRIBUTIONS_CACHE_SIZE);
129+
const profileCache = new TTLCache<GitHubUserProfile>(MAX_PROFILE_CACHE_SIZE);
130+
const reposCache = new TTLCache<GitHubRepo[]>(MAX_REPOS_CACHE_SIZE);
120131

121132
function cacheKey(
122133
kind: 'contributions' | 'profile' | 'repos',

0 commit comments

Comments
 (0)