brainy/dist/storage/enhancedCacheManager.js
David Snelling f8c45f2d8d Initial commit: Brainy - Multi-Dimensional AI Database
Open source vector database with HNSW indexing, graph relationships,
and metadata facets. Features CLI with professional augmentation registry
integration for discovering extensions and capabilities.
2025-08-18 17:35:06 -07:00

520 lines
No EOL
18 KiB
JavaScript

/**
* Enhanced Multi-Level Cache Manager with Predictive Prefetching
* Optimized for HNSW search patterns and large-scale vector operations
*/
// Prefetch prediction strategies
var PrefetchStrategy;
(function (PrefetchStrategy) {
PrefetchStrategy["GRAPH_CONNECTIVITY"] = "connectivity";
PrefetchStrategy["VECTOR_SIMILARITY"] = "similarity";
PrefetchStrategy["ACCESS_PATTERN"] = "pattern";
PrefetchStrategy["HYBRID"] = "hybrid";
})(PrefetchStrategy || (PrefetchStrategy = {}));
/**
* Enhanced cache manager with intelligent prefetching for HNSW operations
* Provides multi-level caching optimized for vector search workloads
*/
export class EnhancedCacheManager {
constructor(config = {}) {
this.hotCache = new Map();
this.warmCache = new Map();
this.prefetchQueue = new Set();
this.accessPatterns = new Map(); // Track access times
this.vectorIndex = new Map(); // For similarity calculations
this.prefetchInProgress = false;
// Statistics and monitoring
this.stats = {
hotCacheHits: 0,
hotCacheMisses: 0,
warmCacheHits: 0,
warmCacheMisses: 0,
prefetchHits: 0,
prefetchMisses: 0,
totalPrefetched: 0,
predictionAccuracy: 0,
backgroundOptimizations: 0
};
this.config = {
hotCacheMaxSize: 1000,
hotCacheEvictionThreshold: 0.8,
warmCacheMaxSize: 10000,
warmCacheTTL: 300000, // 5 minutes
prefetchEnabled: true,
prefetchStrategy: PrefetchStrategy.HYBRID,
prefetchBatchSize: 50,
predictionLookahead: 3,
similarityThreshold: 0.8,
maxSimilarityDistance: 2.0,
backgroundOptimization: true,
statisticsCollection: true,
...config
};
// Start background optimization if enabled
if (this.config.backgroundOptimization) {
this.startBackgroundOptimization();
}
}
/**
* Set storage adapters for warm/cold storage operations
*/
setStorageAdapters(storageAdapter, batchOperations) {
this.storageAdapter = storageAdapter;
this.batchOperations = batchOperations;
}
/**
* Get item with intelligent prefetching
*/
async get(id) {
const startTime = Date.now();
// Update access pattern
this.recordAccess(id, startTime);
// Check hot cache first
let entry = this.hotCache.get(id);
if (entry && !this.isExpired(entry)) {
entry.lastAccessed = startTime;
entry.accessCount++;
this.stats.hotCacheHits++;
// Trigger predictive prefetch
if (this.config.prefetchEnabled) {
this.schedulePrefetch(id, entry.data);
}
return entry.data;
}
this.stats.hotCacheMisses++;
// Check warm cache
entry = this.warmCache.get(id);
if (entry && !this.isExpired(entry)) {
entry.lastAccessed = startTime;
entry.accessCount++;
this.stats.warmCacheHits++;
// Promote to hot cache if frequently accessed
if (entry.accessCount > 3) {
this.promoteToHotCache(id, entry);
}
return entry.data;
}
this.stats.warmCacheMisses++;
// Load from storage
const item = await this.loadFromStorage(id);
if (item) {
// Cache the item
await this.set(id, item);
// Trigger predictive prefetch
if (this.config.prefetchEnabled) {
this.schedulePrefetch(id, item);
}
}
return item;
}
/**
* Get multiple items efficiently with batch operations
*/
async getMany(ids) {
const result = new Map();
const uncachedIds = [];
// Check caches first
for (const id of ids) {
const cached = await this.get(id);
if (cached) {
result.set(id, cached);
}
else {
uncachedIds.push(id);
}
}
// Batch load uncached items
if (uncachedIds.length > 0 && this.batchOperations) {
const batchResult = await this.batchOperations.batchGetNodes(uncachedIds);
// Cache loaded items
for (const [id, item] of batchResult.items) {
await this.set(id, item);
result.set(id, item);
}
}
return result;
}
/**
* Set item in cache with metadata
*/
async set(id, item) {
const now = Date.now();
const entry = {
data: item,
lastAccessed: now,
accessCount: 1,
expiresAt: now + this.config.warmCacheTTL,
connectedNodes: this.extractConnectedNodes(item),
predictionScore: 0
};
// Store vector for similarity calculations
if ('vector' in item && item.vector) {
this.vectorIndex.set(id, item.vector);
entry.vectorSimilarity = 0;
}
// Add to warm cache initially
this.warmCache.set(id, entry);
// Clean up if needed
if (this.warmCache.size > this.config.warmCacheMaxSize) {
this.evictFromWarmCache();
}
// Update statistics
this.stats.warmCacheHits++; // Count as a potential future hit
}
/**
* Intelligent prefetch based on access patterns and graph structure
*/
async schedulePrefetch(currentId, currentItem) {
if (this.prefetchInProgress || !this.config.prefetchEnabled) {
return;
}
// Use different strategies based on configuration
let candidateIds = [];
switch (this.config.prefetchStrategy) {
case PrefetchStrategy.GRAPH_CONNECTIVITY:
candidateIds = this.predictByConnectivity(currentId, currentItem);
break;
case PrefetchStrategy.VECTOR_SIMILARITY:
candidateIds = await this.predictBySimilarity(currentId, currentItem);
break;
case PrefetchStrategy.ACCESS_PATTERN:
candidateIds = this.predictByAccessPattern(currentId);
break;
case PrefetchStrategy.HYBRID:
candidateIds = await this.hybridPrediction(currentId, currentItem);
break;
}
// Filter out already cached items
const uncachedIds = candidateIds.filter(id => !this.hotCache.has(id) && !this.warmCache.has(id)).slice(0, this.config.prefetchBatchSize);
if (uncachedIds.length > 0) {
this.executePrefetch(uncachedIds);
}
}
/**
* Predict next nodes based on graph connectivity
*/
predictByConnectivity(currentId, currentItem) {
const candidates = [];
if ('connections' in currentItem && currentItem.connections) {
const connections = currentItem.connections;
// Add immediate neighbors with higher priority for lower levels
for (const [level, nodeIds] of connections.entries()) {
const priority = Math.max(1, 5 - level); // Higher priority for level 0
for (const nodeId of nodeIds) {
// Add based on priority
for (let i = 0; i < priority; i++) {
candidates.push(nodeId);
}
}
}
}
// Shuffle and deduplicate
const shuffled = candidates.sort(() => Math.random() - 0.5);
return [...new Set(shuffled)];
}
/**
* Predict next nodes based on vector similarity
*/
async predictBySimilarity(currentId, currentItem) {
if (!('vector' in currentItem) || !currentItem.vector) {
return [];
}
const currentVector = currentItem.vector;
const similarities = [];
// Calculate similarities with vectors in cache
for (const [id, vector] of this.vectorIndex.entries()) {
if (id === currentId)
continue;
const similarity = this.cosineSimilarity(currentVector, vector);
if (similarity > this.config.similarityThreshold) {
similarities.push([id, similarity]);
}
}
// Sort by similarity and return top candidates
similarities.sort((a, b) => b[1] - a[1]);
return similarities.slice(0, this.config.prefetchBatchSize).map(([id]) => id);
}
/**
* Predict based on historical access patterns
*/
predictByAccessPattern(currentId) {
const currentPattern = this.accessPatterns.get(currentId);
if (!currentPattern || currentPattern.length < 2) {
return [];
}
// Find similar access patterns
const candidates = [];
for (const [id, pattern] of this.accessPatterns.entries()) {
if (id === currentId || pattern.length < 2)
continue;
const similarity = this.patternSimilarity(currentPattern, pattern);
if (similarity > 0.5) {
candidates.push([id, similarity]);
}
}
candidates.sort((a, b) => b[1] - a[1]);
return candidates.slice(0, this.config.prefetchBatchSize).map(([id]) => id);
}
/**
* Hybrid prediction combining multiple strategies
*/
async hybridPrediction(currentId, currentItem) {
const connectivityCandidates = this.predictByConnectivity(currentId, currentItem);
const similarityCandidates = await this.predictBySimilarity(currentId, currentItem);
const patternCandidates = this.predictByAccessPattern(currentId);
// Weighted combination
const candidateScores = new Map();
// Connectivity gets highest weight (40%)
connectivityCandidates.forEach((id, index) => {
const score = (connectivityCandidates.length - index) / connectivityCandidates.length * 0.4;
candidateScores.set(id, (candidateScores.get(id) || 0) + score);
});
// Similarity gets medium weight (35%)
similarityCandidates.forEach((id, index) => {
const score = (similarityCandidates.length - index) / similarityCandidates.length * 0.35;
candidateScores.set(id, (candidateScores.get(id) || 0) + score);
});
// Pattern gets lower weight (25%)
patternCandidates.forEach((id, index) => {
const score = (patternCandidates.length - index) / patternCandidates.length * 0.25;
candidateScores.set(id, (candidateScores.get(id) || 0) + score);
});
// Sort by combined score
const sortedCandidates = Array.from(candidateScores.entries())
.sort((a, b) => b[1] - a[1])
.map(([id]) => id);
return sortedCandidates.slice(0, this.config.prefetchBatchSize);
}
/**
* Execute prefetch operation in background
*/
async executePrefetch(ids) {
if (this.prefetchInProgress || !this.batchOperations) {
return;
}
this.prefetchInProgress = true;
try {
const batchResult = await this.batchOperations.batchGetNodes(ids);
// Cache prefetched items
for (const [id, item] of batchResult.items) {
const entry = {
data: item,
lastAccessed: Date.now(),
accessCount: 0, // Prefetched items start with 0 access count
expiresAt: Date.now() + this.config.warmCacheTTL,
connectedNodes: this.extractConnectedNodes(item),
predictionScore: 1 // Mark as prefetched
};
this.warmCache.set(id, entry);
}
this.stats.totalPrefetched += batchResult.items.size;
}
catch (error) {
console.warn('Prefetch operation failed:', error);
}
finally {
this.prefetchInProgress = false;
}
}
/**
* Load item from storage adapter
*/
async loadFromStorage(id) {
if (!this.storageAdapter) {
return null;
}
try {
return await this.storageAdapter.get(id);
}
catch (error) {
console.warn(`Failed to load ${id} from storage:`, error);
return null;
}
}
/**
* Promote frequently accessed item to hot cache
*/
promoteToHotCache(id, entry) {
// Remove from warm cache
this.warmCache.delete(id);
// Add to hot cache
this.hotCache.set(id, entry);
// Evict if necessary
if (this.hotCache.size > this.config.hotCacheMaxSize) {
this.evictFromHotCache();
}
}
/**
* Evict least recently used items from hot cache
*/
evictFromHotCache() {
const threshold = Math.floor(this.config.hotCacheMaxSize * this.config.hotCacheEvictionThreshold);
if (this.hotCache.size <= threshold) {
return;
}
// Sort by last accessed time and access count
const entries = Array.from(this.hotCache.entries())
.sort((a, b) => {
const scoreA = a[1].accessCount * 0.7 + (Date.now() - a[1].lastAccessed) * -0.3;
const scoreB = b[1].accessCount * 0.7 + (Date.now() - b[1].lastAccessed) * -0.3;
return scoreA - scoreB;
});
// Remove least valuable entries
const toRemove = entries.slice(0, this.hotCache.size - threshold);
for (const [id] of toRemove) {
this.hotCache.delete(id);
}
}
/**
* Evict expired items from warm cache
*/
evictFromWarmCache() {
const now = Date.now();
const toRemove = [];
for (const [id, entry] of this.warmCache.entries()) {
if (this.isExpired(entry)) {
toRemove.push(id);
}
}
// Remove expired items
for (const id of toRemove) {
this.warmCache.delete(id);
this.vectorIndex.delete(id);
}
// If still over limit, remove LRU items
if (this.warmCache.size > this.config.warmCacheMaxSize) {
const entries = Array.from(this.warmCache.entries())
.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed);
const excess = this.warmCache.size - this.config.warmCacheMaxSize;
for (let i = 0; i < excess; i++) {
const [id] = entries[i];
this.warmCache.delete(id);
this.vectorIndex.delete(id);
}
}
}
/**
* Record access pattern for prediction
*/
recordAccess(id, timestamp) {
if (!this.config.statisticsCollection) {
return;
}
let pattern = this.accessPatterns.get(id);
if (!pattern) {
pattern = [];
this.accessPatterns.set(id, pattern);
}
pattern.push(timestamp);
// Keep only recent accesses (last 10)
if (pattern.length > 10) {
pattern.shift();
}
}
/**
* Extract connected node IDs from HNSW item
*/
extractConnectedNodes(item) {
const connected = new Set();
if ('connections' in item && item.connections) {
const connections = item.connections;
for (const nodeIds of connections.values()) {
nodeIds.forEach(id => connected.add(id));
}
}
return connected;
}
/**
* Check if cache entry is expired
*/
isExpired(entry) {
return entry.expiresAt !== null && Date.now() > entry.expiresAt;
}
/**
* Calculate cosine similarity between vectors
*/
cosineSimilarity(a, b) {
if (a.length !== b.length)
return 0;
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
return magnitude === 0 ? 0 : dotProduct / magnitude;
}
/**
* Calculate pattern similarity between access patterns
*/
patternSimilarity(pattern1, pattern2) {
const minLength = Math.min(pattern1.length, pattern2.length);
if (minLength < 2)
return 0;
// Calculate intervals between accesses
const intervals1 = pattern1.slice(1).map((t, i) => t - pattern1[i]);
const intervals2 = pattern2.slice(1).map((t, i) => t - pattern2[i]);
// Compare interval patterns
let similarity = 0;
const compareLength = Math.min(intervals1.length, intervals2.length);
for (let i = 0; i < compareLength; i++) {
const diff = Math.abs(intervals1[i] - intervals2[i]);
const maxInterval = Math.max(intervals1[i], intervals2[i]);
similarity += maxInterval === 0 ? 1 : 1 - (diff / maxInterval);
}
return compareLength === 0 ? 0 : similarity / compareLength;
}
/**
* Start background optimization process
*/
startBackgroundOptimization() {
setInterval(() => {
this.runBackgroundOptimization();
}, 60000); // Run every minute
}
/**
* Run background optimization tasks
*/
runBackgroundOptimization() {
// Clean up expired entries
this.evictFromWarmCache();
this.evictFromHotCache();
// Clean up old access patterns
const cutoff = Date.now() - 3600000; // 1 hour
for (const [id, pattern] of this.accessPatterns.entries()) {
const recentAccesses = pattern.filter(t => t > cutoff);
if (recentAccesses.length === 0) {
this.accessPatterns.delete(id);
}
else {
this.accessPatterns.set(id, recentAccesses);
}
}
this.stats.backgroundOptimizations++;
}
/**
* Get cache statistics
*/
getStats() {
return {
...this.stats,
hotCacheSize: this.hotCache.size,
warmCacheSize: this.warmCache.size,
prefetchQueueSize: this.prefetchQueue.size,
accessPatternsTracked: this.accessPatterns.size
};
}
/**
* Clear all caches
*/
clear() {
this.hotCache.clear();
this.warmCache.clear();
this.prefetchQueue.clear();
this.accessPatterns.clear();
this.vectorIndex.clear();
}
}
//# sourceMappingURL=enhancedCacheManager.js.map