Fix: Prevent cache memory leak by adding maxKeys limit and conditional caching

- Add maxKeys limit (10,000) to NodeCache to prevent unbounded memory growth
- Skip caching undefined values when GeoIP/ASN lookups fail (e.g., when MaxMind DB not configured)
- Add periodic cache statistics logging every 5 minutes for monitoring
- Fixes memory leak where cache would grow indefinitely with high request volumes

The maxKeys limit uses LRU eviction, so oldest entries are automatically removed
when the limit is reached. With ~10k requests/day and 5min TTL, 10k keys provides
ample headroom while preventing OOM issues.

Fixes #2120
This commit is contained in:
Dhananjay Mahtha
2025-12-21 16:53:25 +05:30
committed by Owen Schwartz
parent 9e68c6c004
commit 90c48f20e0
3 changed files with 26 additions and 5 deletions

View File

@@ -1,5 +1,20 @@
import NodeCache from "node-cache";
import logger from "@server/logger";
export const cache = new NodeCache({ stdTTL: 3600, checkperiod: 120 });
// Create cache with maxKeys limit to prevent memory leaks
// With ~10k requests/day and 5min TTL, 10k keys should be more than sufficient
export const cache = new NodeCache({
stdTTL: 3600,
checkperiod: 120,
maxKeys: 10000
});
// Log cache statistics periodically for monitoring
setInterval(() => {
const stats = cache.getStats();
logger.debug(
`Cache stats - Keys: ${stats.keys}, Hits: ${stats.hits}, Misses: ${stats.misses}, Hit rate: ${stats.hits > 0 ? ((stats.hits / (stats.hits + stats.misses)) * 100).toFixed(2) : 0}%`
);
}, 300000); // Every 5 minutes
export default cache;

View File

@@ -161,8 +161,11 @@ async function getCountryCodeFromIp(ip: string): Promise<string | undefined> {
if (!cachedCountryCode) {
cachedCountryCode = await getCountryCodeForIp(ip); // do it locally
// Cache for longer since IP geolocation doesn't change frequently
cache.set(geoIpCacheKey, cachedCountryCode, 300); // 5 minutes
// Only cache successful lookups to avoid filling cache with undefined values
if (cachedCountryCode) {
// Cache for longer since IP geolocation doesn't change frequently
cache.set(geoIpCacheKey, cachedCountryCode, 300); // 5 minutes
}
}
return cachedCountryCode;

View File

@@ -1206,8 +1206,11 @@ async function getCountryCodeFromIp(ip: string): Promise<string | undefined> {
if (!cachedCountryCode) {
cachedCountryCode = await getCountryCodeForIp(ip); // do it locally
// Cache for longer since IP geolocation doesn't change frequently
cache.set(geoIpCacheKey, cachedCountryCode, 300); // 5 minutes
// Only cache successful lookups to avoid filling cache with undefined values
if (cachedCountryCode) {
// Cache for longer since IP geolocation doesn't change frequently
cache.set(geoIpCacheKey, cachedCountryCode, 300); // 5 minutes
}
}
return cachedCountryCode;