docs: remove Phase 2 completion and executive summary files

This commit is contained in:
2025-10-23 11:50:27 -04:00
parent 291b2e61b0
commit 69df390f58
47 changed files with 12901 additions and 523 deletions
+134
View File
@@ -0,0 +1,134 @@
/**
* MetadataCache - TTL + simple LRU cache
*
* Features:
* - TTL-based expiration
* - Simple FIFO eviction when maxItems exceeded
* - read-through helper for async producers
* - Pseudo-LRU via Map re-insertion
*/
export class MetadataCache {
constructor({ ttlMs = 5 * 60 * 1000, maxItems = 10_000 } = {}) {
this.ttlMs = ttlMs;
this.maxItems = maxItems;
this.store = new Map(); // key -> { value, exp }
this.stats = {
hits: 0,
misses: 0,
evictions: 0,
sets: 0
};
}
_now() {
return Date.now();
}
/**
* Evict oldest entries if cache exceeds maxItems
*/
_evictIfNeeded() {
if (this.store.size <= this.maxItems) return;
const toEvict = this.store.size - this.maxItems + Math.floor(this.maxItems * 0.1);
let evicted = 0;
for (const k of this.store.keys()) {
this.store.delete(k);
evicted++;
if (evicted >= toEvict) break;
}
this.stats.evictions += evicted;
}
/**
* Get value from cache
* Returns { hit: boolean, value: any }
*/
get(key) {
const entry = this.store.get(key);
if (!entry) {
this.stats.misses++;
return { hit: false, value: undefined };
}
// Check expiration
if (entry.exp < this._now()) {
this.store.delete(key);
this.stats.misses++;
return { hit: false, value: undefined };
}
// Touch for pseudo-LRU: re-insert at end
this.store.delete(key);
this.store.set(key, entry);
this.stats.hits++;
return { hit: true, value: entry.value };
}
/**
* Set value in cache with optional TTL override
*/
set(key, value, { ttlMs = this.ttlMs } = {}) {
const exp = this._now() + ttlMs;
this.store.set(key, { value, exp });
this.stats.sets++;
this._evictIfNeeded();
}
/**
* Delete specific key
*/
delete(key) {
this.store.delete(key);
}
/**
* Clear entire cache
*/
clear() {
this.store.clear();
}
/**
* Read-through helper: get from cache or call producer
* Returns { value, hit: boolean }
*/
async remember(key, producer, { ttlMs = this.ttlMs } = {}) {
const { hit, value } = this.get(key);
if (hit) return { value, hit: true };
const fresh = await producer();
this.set(key, fresh, { ttlMs });
return { value: fresh, hit: false };
}
/**
* Get cache statistics
*/
getStats() {
const total = this.stats.hits + this.stats.misses;
const hitRate = total > 0 ? (this.stats.hits / total) * 100 : 0;
return {
size: this.store.size,
maxItems: this.maxItems,
ttlMs: this.ttlMs,
hits: this.stats.hits,
misses: this.stats.misses,
hitRate: Math.round(hitRate * 10) / 10,
evictions: this.stats.evictions,
sets: this.stats.sets
};
}
/**
* Reset statistics
*/
resetStats() {
this.stats = { hits: 0, misses: 0, evictions: 0, sets: 0 };
}
}
+137
View File
@@ -0,0 +1,137 @@
/**
* PerformanceMonitor - Track latencies, errors, and cache metrics
*
* Features:
* - Request timing (avg, p95)
* - Cache hit/miss tracking
* - Error counting
* - Simple ring buffer for timings
*/
export class PerformanceMonitor {
constructor(clock = () => Date.now()) {
this.clock = clock;
this.counters = {
cacheHit: 0,
cacheMiss: 0,
reqCount: 0,
reqError: 0,
meilisearchRetry: 0,
filesystemRetry: 0
};
this.timings = []; // Ring buffer for latencies
this.maxTimings = 500;
this.startTime = Date.now();
}
/**
* Mark request start, returns timestamp
*/
markRequestStart() {
return this.clock();
}
/**
* Mark request end, calculate duration
* Returns duration in ms
*/
markRequestEnd(startTs, ok = true) {
const dur = this.clock() - startTs;
if (this.timings.length >= this.maxTimings) {
this.timings.shift();
}
this.timings.push(dur);
this.counters.reqCount += 1;
if (!ok) this.counters.reqError += 1;
return dur;
}
/**
* Mark cache hit or miss
*/
markCache(hit) {
if (hit) {
this.counters.cacheHit += 1;
} else {
this.counters.cacheMiss += 1;
}
}
/**
* Mark retry event
*/
markRetry(source = 'unknown') {
if (source === 'meilisearch') {
this.counters.meilisearchRetry += 1;
} else if (source === 'filesystem') {
this.counters.filesystemRetry += 1;
}
}
/**
* Get performance snapshot
*/
snapshot() {
const arr = this.timings.slice();
const n = arr.length || 1;
const sum = arr.reduce((a, b) => a + b, 0);
const avg = sum / n;
// Calculate p95
const sorted = arr.slice().sort((a, b) => a - b);
const p95Idx = Math.min(n - 1, Math.floor(n * 0.95));
const p95 = sorted[p95Idx] || 0;
const uptime = this.clock() - this.startTime;
const cacheTotal = this.counters.cacheHit + this.counters.cacheMiss;
const cacheHitRate = cacheTotal > 0
? Math.round((this.counters.cacheHit / cacheTotal) * 1000) / 10
: 0;
const errorRate = this.counters.reqCount > 0
? Math.round((this.counters.reqError / this.counters.reqCount) * 1000) / 10
: 0;
return {
uptime,
requests: {
total: this.counters.reqCount,
errors: this.counters.reqError,
errorRate: `${errorRate}%`
},
cache: {
hits: this.counters.cacheHit,
misses: this.counters.cacheMiss,
hitRate: `${cacheHitRate}%`
},
retries: {
meilisearch: this.counters.meilisearchRetry,
filesystem: this.counters.filesystemRetry
},
latency: {
avgMs: Math.round(avg),
p95Ms: Math.round(p95),
samples: this.timings.length
}
};
}
/**
* Reset all counters
*/
reset() {
this.counters = {
cacheHit: 0,
cacheMiss: 0,
reqCount: 0,
reqError: 0,
meilisearchRetry: 0,
filesystemRetry: 0
};
this.timings = [];
this.startTime = this.clock();
}
}