UNPKG

mem100x

Version:

⚡ The FASTEST MCP memory server ever built - 66k+ entities/sec with intelligent context detection

199 lines 5.82 kB
"use strict"; /** * Two-Queue (2Q) Cache Implementation * * 2Q algorithm uses three structures: * - Am: Fast FIFO queue for recent items (admission queue) * - A1in: FIFO queue for items seen once * - A1out: Ghost entries (keys only) of recently evicted from A1in * * Items graduate from A1in to Am only if accessed again while in A1out. * This filters out one-time access patterns efficiently. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.TwoQueueCache = void 0; class FIFOQueue { head = null; tail = null; nodeMap = new Map(); size = 0; enqueue(key, value) { const node = { key, value, prev: null, next: null }; if (!this.head) { this.head = this.tail = node; } else { node.next = this.head; this.head.prev = node; this.head = node; } this.nodeMap.set(key, node); this.size++; } dequeue() { if (!this.tail) return null; const result = { key: this.tail.key, value: this.tail.value }; this.nodeMap.delete(this.tail.key); if (this.tail.prev) { this.tail = this.tail.prev; this.tail.next = null; } else { this.head = this.tail = null; } this.size--; return result; } remove(key) { const node = this.nodeMap.get(key); if (!node) return false; if (node.prev) node.prev.next = node.next; if (node.next) node.next.prev = node.prev; if (node === this.head) this.head = node.next; if (node === this.tail) this.tail = node.prev; this.nodeMap.delete(key); this.size--; return true; } has(key) { return this.nodeMap.has(key); } get(key) { const node = this.nodeMap.get(key); return node?.value; } clear() { this.head = this.tail = null; this.nodeMap.clear(); this.size = 0; } keys() { return Array.from(this.nodeMap.keys()); } } class TwoQueueCache { maxSize; kinSize; // Size of A1in (25% of total) koutSize; // Size of A1out (50% of total) am; // Main cache (hot items) a1in; // Recent items (seen once) a1out; // Ghost entries (recently evicted) // Performance metrics hits = 0; misses = 0; constructor(maxSize = 1000) { this.maxSize = maxSize; // Recommended 2Q parameters from paper this.kinSize = Math.floor(maxSize * 0.25); this.koutSize = Math.floor(maxSize * 0.5); this.am = new FIFOQueue(); this.a1in = new FIFOQueue(); this.a1out = new FIFOQueue(); } get(key) { // Check Am (hot items) if (this.am.has(key)) { this.hits++; return this.am.get(key); } // Check A1in (recent items) if (this.a1in.has(key)) { this.hits++; return this.a1in.get(key); } // Not found in cache this.misses++; return undefined; } set(key, value) { // If already in Am, just update (no movement in FIFO) if (this.am.has(key)) { // Remove and re-add to update value this.am.remove(key); this.am.enqueue(key, value); return; } // If in A1in, it's getting accessed again - promote to Am if (this.a1in.has(key)) { this.a1in.remove(key); this.promoteToAm(key, value); return; } // If in A1out (ghost list), it's a recent re-reference - goes to Am if (this.a1out.has(key)) { this.a1out.remove(key); this.promoteToAm(key, value); return; } // New item - goes to A1in this.addToA1in(key, value); } promoteToAm(key, value) { // Add to Am this.am.enqueue(key, value); // Evict from Am if necessary if (this.am.size > this.maxSize - this.kinSize) { const evicted = this.am.dequeue(); // Items evicted from Am are gone (not ghosts) } } addToA1in(key, value) { // Add to A1in this.a1in.enqueue(key, value); // Evict from A1in if necessary if (this.a1in.size > this.kinSize) { const evicted = this.a1in.dequeue(); if (evicted) { // Move to ghost list A1out (key only, no value) this.a1out.enqueue(evicted.key); // Maintain A1out size if (this.a1out.size > this.koutSize) { this.a1out.dequeue(); } } } } has(key) { return this.am.has(key) || this.a1in.has(key); } delete(key) { return this.am.remove(key) || this.a1in.remove(key) || this.a1out.remove(key); } clear() { this.am.clear(); this.a1in.clear(); this.a1out.clear(); this.hits = 0; this.misses = 0; } getStats() { const total = this.hits + this.misses; return { hits: this.hits, misses: this.misses, hitRate: total > 0 ? this.hits / total : 0, size: this.am.size + this.a1in.size, distribution: { am: this.am.size, a1in: this.a1in.size, a1out: this.a1out.size } }; } // Get current state for debugging getState() { return { am: this.am.keys(), a1in: this.a1in.keys(), a1out: this.a1out.keys() }; } } exports.TwoQueueCache = TwoQueueCache; //# sourceMappingURL=two-queue-cache.js.map