webupdatev1
This commit is contained in:
@@ -5,28 +5,63 @@
|
||||
const logger = require("../config/logger");
|
||||
|
||||
class CacheManager {
|
||||
constructor(defaultTTL = 300000) {
|
||||
// 5 minutes default
|
||||
constructor(defaultTTL = 300000, maxSize = 2000) {
|
||||
// 5 minutes default, max 2000 entries (optimized for performance)
|
||||
this.cache = new Map();
|
||||
this.defaultTTL = defaultTTL;
|
||||
this.maxSize = maxSize;
|
||||
this.stats = { hits: 0, misses: 0, evictions: 0 };
|
||||
// Use Map for O(1) LRU tracking instead of array indexOf/splice
|
||||
this.lruHead = null; // Most recently used
|
||||
this.lruTail = null; // Least recently used
|
||||
this.lruNodes = new Map(); // key -> {prev, next, key}
|
||||
}
|
||||
|
||||
set(key, value, ttl = this.defaultTTL) {
|
||||
const expiresAt = Date.now() + ttl;
|
||||
|
||||
// If key exists, remove from LRU list first
|
||||
if (this.cache.has(key)) {
|
||||
this._removeLRUNode(key);
|
||||
} else if (this.cache.size >= this.maxSize) {
|
||||
// Evict least recently used
|
||||
if (this.lruTail) {
|
||||
const evictKey = this.lruTail.key;
|
||||
this.cache.delete(evictKey);
|
||||
this._removeLRUNode(evictKey);
|
||||
this.stats.evictions++;
|
||||
logger.debug(`Cache LRU eviction: ${evictKey}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.cache.set(key, { value, expiresAt });
|
||||
this._addLRUNode(key); // Add to head (most recent)
|
||||
logger.debug(`Cache set: ${key} (TTL: ${ttl}ms)`);
|
||||
}
|
||||
|
||||
get(key) {
|
||||
const cached = this.cache.get(key);
|
||||
if (!cached) return null;
|
||||
|
||||
if (Date.now() > cached.expiresAt) {
|
||||
if (!cached) {
|
||||
this.stats.misses++;
|
||||
logger.debug(`Cache miss: ${key}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
if (now > cached.expiresAt) {
|
||||
this.cache.delete(key);
|
||||
this._removeLRUNode(key);
|
||||
this.stats.misses++;
|
||||
logger.debug(`Cache expired: ${key}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Move to head (most recently used) - O(1)
|
||||
this._removeLRUNode(key);
|
||||
this._addLRUNode(key);
|
||||
|
||||
this.stats.hits++;
|
||||
logger.debug(`Cache hit: ${key}`);
|
||||
return cached.value;
|
||||
}
|
||||
@@ -53,6 +88,9 @@ class CacheManager {
|
||||
clear() {
|
||||
const size = this.cache.size;
|
||||
this.cache.clear();
|
||||
this.lruNodes.clear();
|
||||
this.lruHead = null;
|
||||
this.lruTail = null;
|
||||
logger.info(`Cache cleared (${size} keys)`);
|
||||
}
|
||||
|
||||
@@ -60,6 +98,63 @@ class CacheManager {
|
||||
return this.cache.size;
|
||||
}
|
||||
|
||||
// Get cache statistics
|
||||
getStats() {
|
||||
const hitRate =
|
||||
this.stats.hits + this.stats.misses > 0
|
||||
? (
|
||||
(this.stats.hits / (this.stats.hits + this.stats.misses)) *
|
||||
100
|
||||
).toFixed(2)
|
||||
: 0;
|
||||
return {
|
||||
...this.stats,
|
||||
hitRate: `${hitRate}%`,
|
||||
size: this.cache.size,
|
||||
maxSize: this.maxSize,
|
||||
};
|
||||
}
|
||||
|
||||
// Reset statistics
|
||||
resetStats() {
|
||||
this.stats = { hits: 0, misses: 0, evictions: 0 };
|
||||
}
|
||||
|
||||
// O(1) LRU operations using doubly-linked list pattern
|
||||
_addLRUNode(key) {
|
||||
const node = { key, prev: null, next: this.lruHead };
|
||||
|
||||
if (this.lruHead) {
|
||||
this.lruHead.prev = node;
|
||||
}
|
||||
this.lruHead = node;
|
||||
|
||||
if (!this.lruTail) {
|
||||
this.lruTail = node;
|
||||
}
|
||||
|
||||
this.lruNodes.set(key, node);
|
||||
}
|
||||
|
||||
_removeLRUNode(key) {
|
||||
const node = this.lruNodes.get(key);
|
||||
if (!node) return;
|
||||
|
||||
if (node.prev) {
|
||||
node.prev.next = node.next;
|
||||
} else {
|
||||
this.lruHead = node.next;
|
||||
}
|
||||
|
||||
if (node.next) {
|
||||
node.next.prev = node.prev;
|
||||
} else {
|
||||
this.lruTail = node.prev;
|
||||
}
|
||||
|
||||
this.lruNodes.delete(key);
|
||||
}
|
||||
|
||||
// Clean up expired entries
|
||||
cleanup() {
|
||||
const now = Date.now();
|
||||
|
||||
Reference in New Issue
Block a user