/** * 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