/** * Scaled HNSW System - Integration of All Optimization Strategies * Production-ready system for handling millions of vectors with sub-second search */ import { Vector, VectorDocument, HNSWConfig } from '../coreTypes.js' import { PartitionedHNSWIndex, PartitionConfig } from './partitionedHNSWIndex.js' import { OptimizedHNSWIndex, OptimizedHNSWConfig } from './optimizedHNSWIndex.js' import { DistributedSearchSystem, SearchStrategy } from './distributedSearch.js' import { EnhancedCacheManager } from '../storage/enhancedCacheManager.js' import { BatchS3Operations } from '../storage/adapters/batchS3Operations.js' import { ReadOnlyOptimizations } from '../storage/readOnlyOptimizations.js' import { euclideanDistance } from '../utils/index.js' import { autoConfigureBrainy, AutoConfiguration } from '../utils/autoConfiguration.js' export interface ScaledHNSWConfig { // Required: Basic dataset expectations (can be auto-detected if not provided) expectedDatasetSize?: number // Auto-detected if not provided maxMemoryUsage?: number // Auto-detected based on environment targetSearchLatency?: number // Auto-configured based on environment // Storage configuration (optional - auto-detects S3 availability) s3Config?: { bucketName: string region: string endpoint?: string accessKeyId?: string // Falls back to env vars secretAccessKey?: string // Falls back to env vars } // Auto-configuration options autoConfigureEnvironment?: boolean // Default: true learningEnabled?: boolean // Default: true - adapts to performance // Manual overrides (optional - auto-configured if not provided) enablePartitioning?: boolean enableCompression?: boolean enableDistributedSearch?: boolean enablePredictiveCaching?: boolean // Advanced manual tuning (optional) partitionConfig?: Partial hnswConfig?: Partial readOnlyMode?: boolean } /** * High-performance HNSW system with all optimizations integrated * Handles datasets from thousands to millions of vectors */ export class ScaledHNSWSystem { private config: ScaledHNSWConfig & { expectedDatasetSize: number maxMemoryUsage: number targetSearchLatency: number autoConfigureEnvironment: boolean learningEnabled: boolean enablePartitioning: boolean enableCompression: boolean enableDistributedSearch: boolean enablePredictiveCaching: boolean readOnlyMode: boolean } private autoConfig: AutoConfiguration private partitionedIndex?: PartitionedHNSWIndex private distributedSearch?: DistributedSearchSystem private cacheManager?: EnhancedCacheManager private batchOperations?: BatchS3Operations private readOnlyOptimizations?: ReadOnlyOptimizations // Performance monitoring and learning private performanceMetrics = { totalSearches: 0, averageSearchTime: 0, cacheHitRate: 0, compressionRatio: 0, memoryUsage: 0, indexSize: 0, lastLearningUpdate: Date.now() } constructor(config: ScaledHNSWConfig = {}) { this.autoConfig = AutoConfiguration.getInstance() // Set basic defaults - these will be overridden by auto-configuration this.config = { expectedDatasetSize: 100000, maxMemoryUsage: 4 * 1024 * 1024 * 1024, targetSearchLatency: 150, autoConfigureEnvironment: true, learningEnabled: true, enablePartitioning: true, enableCompression: true, enableDistributedSearch: true, enablePredictiveCaching: true, readOnlyMode: false, ...config } this.initializeOptimizedSystem() } /** * Initialize the optimized system based on configuration */ private async initializeOptimizedSystem(): Promise { console.log('Initializing Scaled HNSW System with auto-configuration...') // Auto-configure if enabled if (this.config.autoConfigureEnvironment) { const autoConfigResult = await this.autoConfig.detectAndConfigure({ expectedDataSize: this.config.expectedDatasetSize, s3Available: !!this.config.s3Config, memoryBudget: this.config.maxMemoryUsage }) console.log(`Detected environment: ${autoConfigResult.environment}`) console.log(`Available memory: ${(autoConfigResult.availableMemory / 1024 / 1024 / 1024).toFixed(1)}GB`) console.log(`CPU cores: ${autoConfigResult.cpuCores}`) // Override config with auto-detected values this.config = { ...this.config, expectedDatasetSize: autoConfigResult.recommendedConfig.expectedDatasetSize, maxMemoryUsage: autoConfigResult.recommendedConfig.maxMemoryUsage, targetSearchLatency: autoConfigResult.recommendedConfig.targetSearchLatency, enablePartitioning: autoConfigResult.recommendedConfig.enablePartitioning, enableCompression: autoConfigResult.recommendedConfig.enableCompression, enableDistributedSearch: autoConfigResult.recommendedConfig.enableDistributedSearch, enablePredictiveCaching: autoConfigResult.recommendedConfig.enablePredictiveCaching } } // Determine optimal configuration const optimizedConfig = this.calculateOptimalConfiguration() // Initialize partitioned index with semantic partitioning as default if (this.config.enablePartitioning) { this.partitionedIndex = new PartitionedHNSWIndex( { ...optimizedConfig.partitionConfig, partitionStrategy: 'semantic', // Always use semantic for better performance autoTuneSemanticClusters: true // Enable auto-tuning }, optimizedConfig.hnswConfig, euclideanDistance ) console.log('✓ Partitioned index initialized with semantic clustering') } // Initialize distributed search system if (this.config.enableDistributedSearch && this.partitionedIndex) { this.distributedSearch = new DistributedSearchSystem({ maxConcurrentSearches: optimizedConfig.maxConcurrentSearches, searchTimeout: this.config.targetSearchLatency * 5, adaptivePartitionSelection: true, loadBalancing: true }) console.log('✓ Distributed search system initialized') } // Initialize batch S3 operations if (this.config.s3Config) { this.batchOperations = new BatchS3Operations( null as any, // Would be initialized with actual S3 client this.config.s3Config.bucketName, { maxConcurrency: 50, useS3Select: this.config.expectedDatasetSize > 100000 } ) console.log('✓ Batch S3 operations initialized') } // Initialize enhanced caching if (this.config.enablePredictiveCaching) { this.cacheManager = new EnhancedCacheManager({ hotCacheMaxSize: optimizedConfig.hotCacheSize, warmCacheMaxSize: optimizedConfig.warmCacheSize, prefetchEnabled: true, prefetchStrategy: 'hybrid' as any, // Type casting for enum compatibility prefetchBatchSize: 50 }) if (this.batchOperations) { this.cacheManager.setStorageAdapters(null as any, this.batchOperations) } console.log('✓ Enhanced cache manager initialized') } // Initialize read-only optimizations if (this.config.readOnlyMode && this.config.enableCompression) { this.readOnlyOptimizations = new ReadOnlyOptimizations({ compression: { vectorCompression: 'quantization' as any, metadataCompression: 'gzip' as any, quantizationType: 'scalar' as any, quantizationBits: 8 }, segmentSize: optimizedConfig.segmentSize, memoryMapped: true, cacheIndexInMemory: optimizedConfig.cacheIndexInMemory }) console.log('✓ Read-only optimizations initialized') } console.log('Scaled HNSW System ready for', this.config.expectedDatasetSize, 'vectors') } /** * Calculate optimal configuration based on dataset size and constraints */ private calculateOptimalConfiguration(): { partitionConfig: PartitionConfig hnswConfig: OptimizedHNSWConfig hotCacheSize: number warmCacheSize: number maxConcurrentSearches: number segmentSize: number cacheIndexInMemory: boolean } { const size = this.config.expectedDatasetSize const memoryBudget = this.config.maxMemoryUsage let config: any = {} if (size <= 10000) { // Small dataset - optimize for speed config = { partitionConfig: { maxNodesPerPartition: 10000, partitionStrategy: 'hash' as const }, hnswConfig: { M: 16, efConstruction: 200, efSearch: 50, targetSearchLatency: this.config.targetSearchLatency }, hotCacheSize: 1000, warmCacheSize: 5000, maxConcurrentSearches: 4, segmentSize: 5000, cacheIndexInMemory: true } } else if (size <= 100000) { // Medium dataset - balance performance and memory config = { partitionConfig: { maxNodesPerPartition: 25000, partitionStrategy: 'semantic' as const, semanticClusters: 8 }, hnswConfig: { M: 24, efConstruction: 300, efSearch: 75, targetSearchLatency: this.config.targetSearchLatency, dynamicParameterTuning: true }, hotCacheSize: 2000, warmCacheSize: 15000, maxConcurrentSearches: 8, segmentSize: 10000, cacheIndexInMemory: memoryBudget > 2 * 1024 * 1024 * 1024 // 2GB } } else if (size <= 1000000) { // Large dataset - optimize for scale config = { partitionConfig: { maxNodesPerPartition: 50000, partitionStrategy: 'semantic' as const, semanticClusters: 16 }, hnswConfig: { M: 32, efConstruction: 400, efSearch: 100, targetSearchLatency: this.config.targetSearchLatency, dynamicParameterTuning: true, memoryBudget: memoryBudget }, hotCacheSize: 5000, warmCacheSize: 25000, maxConcurrentSearches: 12, segmentSize: 20000, cacheIndexInMemory: memoryBudget > 8 * 1024 * 1024 * 1024 // 8GB } } else { // Very large dataset - maximum optimization config = { partitionConfig: { maxNodesPerPartition: 100000, partitionStrategy: 'hybrid' as const, semanticClusters: 32 }, hnswConfig: { M: 48, efConstruction: 500, efSearch: 150, targetSearchLatency: this.config.targetSearchLatency, dynamicParameterTuning: true, memoryBudget: memoryBudget, diskCacheEnabled: true }, hotCacheSize: 10000, warmCacheSize: 50000, maxConcurrentSearches: 20, segmentSize: 50000, cacheIndexInMemory: false // Too large for memory } } return config } /** * Add vector to the scaled system */ public async addVector(item: VectorDocument): Promise { if (!this.partitionedIndex) { throw new Error('System not properly initialized') } const startTime = Date.now() const result = await this.partitionedIndex.addItem(item) // Update performance metrics this.performanceMetrics.indexSize = this.partitionedIndex.size() return result } /** * Bulk insert vectors with optimizations */ public async bulkInsert(items: VectorDocument[]): Promise { if (!this.partitionedIndex) { throw new Error('System not properly initialized') } console.log(`Starting optimized bulk insert of ${items.length} vectors`) const startTime = Date.now() // Sort items for optimal insertion order const sortedItems = this.optimizeInsertionOrder(items) const results: string[] = [] const batchSize = this.calculateOptimalBatchSize(items.length) // Process in batches for (let i = 0; i < sortedItems.length; i += batchSize) { const batch = sortedItems.slice(i, i + batchSize) for (const item of batch) { const id = await this.partitionedIndex.addItem(item) results.push(id) } // Progress logging if (i % (batchSize * 10) === 0) { const progress = ((i / sortedItems.length) * 100).toFixed(1) console.log(`Bulk insert progress: ${progress}%`) } } const totalTime = Date.now() - startTime console.log(`Bulk insert completed: ${results.length} vectors in ${totalTime}ms`) return results } /** * High-performance vector search with all optimizations */ public async search( queryVector: Vector, k: number = 10, options: { strategy?: SearchStrategy useCache?: boolean maxPartitions?: number } = {} ): Promise> { const startTime = Date.now() try { let results: Array<[string, number]> if (this.distributedSearch && this.partitionedIndex) { // Use distributed search for optimal performance results = await this.distributedSearch.distributedSearch( this.partitionedIndex, queryVector, k, options.strategy || SearchStrategy.ADAPTIVE ) } else if (this.partitionedIndex) { // Fall back to partitioned search results = await this.partitionedIndex.search( queryVector, k, { maxPartitions: options.maxPartitions } ) } else { throw new Error('No search system available') } // Update performance metrics and learn from performance const searchTime = Date.now() - startTime this.updateSearchMetrics(searchTime, results.length) // Adaptive learning - adjust configuration based on performance if (this.config.learningEnabled && this.shouldTriggerLearning()) { await this.adaptivelyLearnFromPerformance() } return results } catch (error) { console.error('Search failed:', error) throw error } } /** * Get system performance metrics */ public getPerformanceMetrics(): typeof this.performanceMetrics & { partitionStats?: any cacheStats?: any compressionStats?: any distributedSearchStats?: any } { const metrics = { ...this.performanceMetrics } // Add subsystem metrics if (this.partitionedIndex) { (metrics as any).partitionStats = this.partitionedIndex.getPartitionStats() } if (this.cacheManager) { (metrics as any).cacheStats = this.cacheManager.getStats() } if (this.readOnlyOptimizations) { (metrics as any).compressionStats = this.readOnlyOptimizations.getCompressionStats() } if (this.distributedSearch) { (metrics as any).distributedSearchStats = this.distributedSearch.getSearchStats() } return metrics } /** * Optimize insertion order for better index quality */ private optimizeInsertionOrder(items: VectorDocument[]): VectorDocument[] { if (items.length < 1000) { return items // Not worth optimizing small batches } // Simple clustering-based approach for better HNSW construction // In production, you might use more sophisticated clustering return items.sort(() => Math.random() - 0.5) } /** * Calculate optimal batch size based on system resources */ private calculateOptimalBatchSize(totalItems: number): number { const memoryBudget = this.config.maxMemoryUsage const estimatedItemSize = 1000 // Rough estimate per item in bytes const maxBatch = Math.floor(memoryBudget * 0.1 / estimatedItemSize) const targetBatch = Math.min(1000, Math.max(100, maxBatch)) return Math.min(targetBatch, totalItems) } /** * Update search performance metrics */ private updateSearchMetrics(searchTime: number, resultCount: number): void { this.performanceMetrics.totalSearches++ this.performanceMetrics.averageSearchTime = (this.performanceMetrics.averageSearchTime + searchTime) / 2 // Update other metrics if (this.cacheManager) { const cacheStats = this.cacheManager.getStats() const totalOps = cacheStats.hotCacheHits + cacheStats.hotCacheMisses + cacheStats.warmCacheHits + cacheStats.warmCacheMisses this.performanceMetrics.cacheHitRate = totalOps > 0 ? (cacheStats.hotCacheHits + cacheStats.warmCacheHits) / totalOps : 0 } if (this.readOnlyOptimizations) { const compressionStats = this.readOnlyOptimizations.getCompressionStats() this.performanceMetrics.compressionRatio = compressionStats.compressionRatio } // Estimate memory usage this.performanceMetrics.memoryUsage = this.estimateMemoryUsage() } /** * Estimate current memory usage */ private estimateMemoryUsage(): number { let totalMemory = 0 if (this.partitionedIndex) { // Rough estimate: 1KB per vector totalMemory += this.partitionedIndex.size() * 1024 } if (this.cacheManager) { const cacheStats = this.cacheManager.getStats() totalMemory += (cacheStats.hotCacheSize + cacheStats.warmCacheSize) * 1024 } return totalMemory } /** * Generate performance report */ public generatePerformanceReport(): string { const metrics = this.getPerformanceMetrics() return ` === Scaled HNSW System Performance Report === Dataset Configuration: - Expected Size: ${this.config.expectedDatasetSize.toLocaleString()} vectors - Current Size: ${metrics.indexSize.toLocaleString()} vectors - Memory Budget: ${(this.config.maxMemoryUsage / 1024 / 1024 / 1024).toFixed(1)}GB - Target Latency: ${this.config.targetSearchLatency}ms Performance Metrics: - Total Searches: ${metrics.totalSearches.toLocaleString()} - Average Search Time: ${metrics.averageSearchTime.toFixed(1)}ms - Cache Hit Rate: ${(metrics.cacheHitRate * 100).toFixed(1)}% - Memory Usage: ${(metrics.memoryUsage / 1024 / 1024).toFixed(1)}MB - Compression Ratio: ${metrics.compressionRatio ? (metrics.compressionRatio * 100).toFixed(1) + '%' : 'N/A'} System Status: ${this.getSystemStatus()} `.trim() } /** * Get overall system status */ private getSystemStatus(): string { const metrics = this.getPerformanceMetrics() if (metrics.averageSearchTime <= this.config.targetSearchLatency) { return '✅ OPTIMAL' } else if (metrics.averageSearchTime <= this.config.targetSearchLatency * 2) { return '⚠️ ACCEPTABLE' } else { return '❌ NEEDS OPTIMIZATION' } } /** * Check if adaptive learning should be triggered */ private shouldTriggerLearning(): boolean { const timeSinceLastLearning = Date.now() - this.performanceMetrics.lastLearningUpdate const minLearningInterval = 30000 // 30 seconds const minSearches = 20 // Minimum searches before learning return timeSinceLastLearning > minLearningInterval && this.performanceMetrics.totalSearches > minSearches && this.performanceMetrics.totalSearches % 50 === 0 // Learn every 50 searches } /** * Adaptively learn from performance and adjust configuration */ private async adaptivelyLearnFromPerformance(): Promise { try { const currentMetrics = { averageSearchTime: this.performanceMetrics.averageSearchTime, memoryUsage: this.performanceMetrics.memoryUsage, cacheHitRate: this.performanceMetrics.cacheHitRate, errorRate: 0 // Could be tracked separately } const adjustments = await this.autoConfig.learnFromPerformance(currentMetrics) if (Object.keys(adjustments).length > 0) { console.log('🧠 Adaptive learning: Adjusting configuration based on performance') // Apply learned adjustments let configChanged = false if (adjustments.enableDistributedSearch !== undefined && adjustments.enableDistributedSearch !== this.config.enableDistributedSearch) { this.config.enableDistributedSearch = adjustments.enableDistributedSearch configChanged = true } if (adjustments.enableCompression !== undefined && adjustments.enableCompression !== this.config.enableCompression) { this.config.enableCompression = adjustments.enableCompression configChanged = true } if (adjustments.enablePredictiveCaching !== undefined && adjustments.enablePredictiveCaching !== this.config.enablePredictiveCaching) { this.config.enablePredictiveCaching = adjustments.enablePredictiveCaching configChanged = true } // Apply partition adjustments if (adjustments.maxNodesPerPartition && this.partitionedIndex && adjustments.maxNodesPerPartition !== this.partitionedIndex.getPartitionStats().averageNodesPerPartition) { // This would require rebuilding the index in a real implementation console.log(`Learning suggests partition size: ${adjustments.maxNodesPerPartition}`) } if (configChanged) { console.log('✅ Configuration updated based on performance learning') } } this.performanceMetrics.lastLearningUpdate = Date.now() } catch (error) { console.warn('Adaptive learning failed:', error) } } /** * Update dataset analysis for better auto-configuration */ public async updateDatasetAnalysis(vectorCount: number, vectorDimension?: number): Promise { if (this.config.autoConfigureEnvironment) { const analysis = { estimatedSize: vectorCount, vectorDimension, accessPatterns: this.inferAccessPatterns() } await this.autoConfig.adaptToDataset(analysis) console.log(`📊 Dataset analysis updated: ${vectorCount} vectors${vectorDimension ? `, ${vectorDimension}D` : ''}`) } } /** * Infer access patterns from current metrics */ private inferAccessPatterns(): 'read-heavy' | 'write-heavy' | 'balanced' { // Simple heuristic - in practice, this would track read/write ratios if (this.performanceMetrics.totalSearches > 100) { return 'read-heavy' } return 'balanced' } /** * Cleanup system resources */ public cleanup(): void { this.distributedSearch?.cleanup() this.cacheManager?.clear() this.readOnlyOptimizations?.cleanup() this.partitionedIndex?.clear() this.autoConfig.resetCache() console.log('Scaled HNSW System cleaned up') } } // Export convenience factory functions /** * Create a fully auto-configured Brainy system - minimal setup required! * Just provide S3 config if you want persistence beyond the current session */ export function createAutoBrainy(s3Config?: { bucketName: string region?: string accessKeyId?: string secretAccessKey?: string }): ScaledHNSWSystem { return new ScaledHNSWSystem({ s3Config: s3Config ? { bucketName: s3Config.bucketName, region: s3Config.region || 'us-east-1', accessKeyId: s3Config.accessKeyId, secretAccessKey: s3Config.secretAccessKey } : undefined, autoConfigureEnvironment: true, learningEnabled: true }) } /** * Create a Brainy system optimized for specific scenarios */ export async function createQuickBrainy( scenario: 'small' | 'medium' | 'large' | 'enterprise', s3Config?: { bucketName: string; region?: string } ): Promise { const { getQuickSetup } = await import('../utils/autoConfiguration.js') const quickConfig = await getQuickSetup(scenario) return new ScaledHNSWSystem({ ...quickConfig, s3Config: s3Config && quickConfig.s3Required ? { bucketName: s3Config.bucketName, region: s3Config.region || 'us-east-1', accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY } : undefined, autoConfigureEnvironment: true, learningEnabled: true }) } /** * Legacy factory function - still works but consider using createAutoBrainy() instead */ export function createScaledHNSWSystem(config: ScaledHNSWConfig = {}): ScaledHNSWSystem { return new ScaledHNSWSystem(config) }