mem100x
Version:
⚡ The FASTEST MCP memory server ever built - 66k+ entities/sec with intelligent context detection
199 lines • 5.82 kB
JavaScript
"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