UNPKG

@codai/memorai-core

Version:

Simplified advanced memory engine - no tiers, just powerful semantic search with persistence

255 lines (254 loc) 9.87 kB
/** * Enterprise-Grade Performance Optimizer for MemorAI * World-class optimization system for production-ready performance */ import { logger } from '../utils/logger.js'; export class PerformanceOptimizer { constructor(config = {}) { this.metrics = []; this.queryCache = new Map(); this.lastGC = Date.now(); this.lastCompaction = Date.now(); this.config = { // Aggressive optimization defaults maxQueryTime: 100, // 100ms max query time batchSize: 5000, // Large batches for efficiency cacheEnabled: true, cacheSize: 10000, // 10k cache entries cacheTTL: 300000, // 5 minutes memoryLimit: 512 * 1024 * 1024, // 512MB limit gcThreshold: 0.8, // GC at 80% memory compactionInterval: 60000, // 1 minute connectionPoolSize: 10, // Large pool connectionTimeout: 5000, // 5 second timeout maxRetries: 3, retryDelay: 100, vectorIndexType: 'hnsw', hnswM: 64, // High-performance HNSW hnswEfConstruct: 400, quantizationEnabled: true, compressionEnabled: true, ...config, }; } /** * Optimize query performance with caching and batch processing */ async optimizeQuery(queryKey, queryFn, options = {}) { const startTime = Date.now(); const { useCache = this.config.cacheEnabled, timeout = this.config.maxQueryTime, } = options; try { // Check cache first if (useCache && this.queryCache.has(queryKey)) { const cached = this.queryCache.get(queryKey); if (cached.expiry > Date.now()) { this.recordMetric('cacheHit', Date.now() - startTime); return cached.data; } else { this.queryCache.delete(queryKey); } } // Execute query with timeout const result = await Promise.race([ queryFn(), this.createTimeoutPromise(timeout), ]); // Cache result if (useCache && this.queryCache.size < this.config.cacheSize) { this.queryCache.set(queryKey, { data: result, expiry: Date.now() + this.config.cacheTTL, }); } this.recordMetric('queryTime', Date.now() - startTime); return result; } catch (error) { this.recordMetric('error', Date.now() - startTime); throw error; } } /** * Batch operations for improved throughput */ async batchProcess(items, processor, batchSize = this.config.batchSize) { const results = []; const batches = this.createBatches(items, batchSize); for (const batch of batches) { try { const batchResults = await processor(batch); results.push(...batchResults); } catch (error) { logger.error('Batch processing error:', error); // Continue with next batch in production } } return results; } /** * Memory management and garbage collection */ async performMemoryOptimization() { const memoryUsage = process.memoryUsage(); const heapUsedMB = memoryUsage.heapUsed / 1024 / 1024; const memoryThreshold = this.config.memoryLimit / 1024 / 1024; if (heapUsedMB > memoryThreshold * this.config.gcThreshold) { logger.info(`Memory optimization triggered: ${heapUsedMB.toFixed(2)}MB used`); // Clear expired cache entries this.clearExpiredCache(); // Force garbage collection if available if (global.gc) { global.gc(); } // Clear old metrics this.cleanupMetrics(); this.lastGC = Date.now(); } // Periodic compaction if (Date.now() - this.lastCompaction > this.config.compactionInterval) { await this.performCompaction(); this.lastCompaction = Date.now(); } } /** * Get optimized configuration for Qdrant */ getQdrantOptimization() { return { collection_config: { vectors: { size: 1536, // OpenAI embedding size distance: 'Cosine', }, optimizers_config: { default_segment_number: 2, max_segment_size: 200000, memmap_threshold: 50000, indexing_threshold: 20000, flush_interval_sec: 5, max_optimization_threads: 4, }, hnsw_config: { m: this.config.hnswM, ef_construct: this.config.hnswEfConstruct, full_scan_threshold: 10000, max_indexing_threads: 4, on_disk: true, }, quantization_config: this.config.quantizationEnabled ? { scalar: { type: 'int8', quantile: 0.99, always_ram: false, }, } : undefined, }, }; } /** * Get real-time performance metrics */ getPerformanceMetrics() { const recentMetrics = this.metrics.slice(-100); // Last 100 operations const avgQueryTime = recentMetrics.length > 0 ? recentMetrics.reduce((sum, m) => sum + m.queryTime, 0) / recentMetrics.length : 0; const cacheHits = recentMetrics.filter(m => m.queryTime < 10).length; // Cache hits are typically <10ms const cacheHitRate = recentMetrics.length > 0 ? cacheHits / recentMetrics.length : 0; const memoryUsage = process.memoryUsage(); return { queryTime: avgQueryTime, memoryUsage: memoryUsage.heapUsed, cacheHitRate, throughput: this.calculateThroughput(), errorRate: this.calculateErrorRate(), connectionCount: 0, // Will be populated by connection pool vectorIndexSize: 0, // Will be populated by vector store timestamp: new Date(), }; } /** * Generate performance optimization recommendations */ generateOptimizationRecommendations() { const metrics = this.getPerformanceMetrics(); const recommendations = []; if (metrics.queryTime > this.config.maxQueryTime) { recommendations.push(`Query time (${metrics.queryTime.toFixed(2)}ms) exceeds target (${this.config.maxQueryTime}ms). Consider increasing cache size or optimizing indexes.`); } if (metrics.cacheHitRate < 0.7) { recommendations.push(`Cache hit rate (${(metrics.cacheHitRate * 100).toFixed(1)}%) is low. Consider increasing cache TTL or cache size.`); } if (metrics.memoryUsage > this.config.memoryLimit * 0.9) { recommendations.push(`Memory usage is approaching limit. Consider reducing batch sizes or enabling compression.`); } if (metrics.errorRate > 0.05) { recommendations.push(`Error rate (${(metrics.errorRate * 100).toFixed(1)}%) is high. Check connection stability and timeout settings.`); } return recommendations; } // Private helper methods createTimeoutPromise(ms) { return new Promise((_, reject) => { setTimeout(() => reject(new Error(`Query timeout after ${ms}ms`)), ms); }); } createBatches(items, batchSize) { const batches = []; for (let i = 0; i < items.length; i += batchSize) { batches.push(items.slice(i, i + batchSize)); } return batches; } clearExpiredCache() { const now = Date.now(); for (const [key, value] of this.queryCache.entries()) { if (value.expiry <= now) { this.queryCache.delete(key); } } } async performCompaction() { logger.info('Performing memory compaction'); // Implement database compaction logic here // This could involve optimizing vector indexes, cleaning up fragmented data, etc. } cleanupMetrics() { // Keep only recent metrics to prevent memory leak if (this.metrics.length > 1000) { this.metrics = this.metrics.slice(-500); } } recordMetric(type, value) { const metric = { queryTime: type === 'queryTime' ? value : 0, memoryUsage: process.memoryUsage().heapUsed, cacheHitRate: type === 'cacheHit' ? 1 : 0, throughput: 0, errorRate: type === 'error' ? 1 : 0, connectionCount: 0, vectorIndexSize: 0, timestamp: new Date(), }; this.metrics.push(metric); } calculateThroughput() { const oneMinuteAgo = Date.now() - 60000; const recentMetrics = this.metrics.filter(m => m.timestamp.getTime() > oneMinuteAgo); return recentMetrics.length; // Operations per minute } calculateErrorRate() { const recentMetrics = this.metrics.slice(-100); if (recentMetrics.length === 0) return 0; const errors = recentMetrics.filter(m => m.errorRate > 0).length; return errors / recentMetrics.length; } } // Singleton instance for global use export const performanceOptimizer = new PerformanceOptimizer();