/** * Read-Only Storage Optimizations for Production Deployments * Implements compression, memory-mapping, and pre-built index segments */ // Compression types supported var CompressionType; (function (CompressionType) { CompressionType["NONE"] = "none"; CompressionType["GZIP"] = "gzip"; CompressionType["BROTLI"] = "brotli"; CompressionType["QUANTIZATION"] = "quantization"; CompressionType["HYBRID"] = "hybrid"; })(CompressionType || (CompressionType = {})); // Vector quantization methods var QuantizationType; (function (QuantizationType) { QuantizationType["SCALAR"] = "scalar"; QuantizationType["PRODUCT"] = "product"; QuantizationType["BINARY"] = "binary"; // Binary quantization })(QuantizationType || (QuantizationType = {})); /** * Read-only storage optimizations for high-performance production deployments */ export class ReadOnlyOptimizations { constructor(config = {}) { this.segments = new Map(); this.compressionStats = { originalSize: 0, compressedSize: 0, compressionRatio: 0, decompressionTime: 0 }; // Quantization codebooks for vector compression this.quantizationCodebooks = new Map(); // Memory-mapped buffers for large datasets this.memoryMappedBuffers = new Map(); this.config = { prebuiltIndexPath: '', memoryMapped: true, compression: { vectorCompression: CompressionType.QUANTIZATION, metadataCompression: CompressionType.GZIP, quantizationType: QuantizationType.SCALAR, quantizationBits: 8, compressionLevel: 6 }, segmentSize: 10000, // 10k nodes per segment prefetchSegments: 3, cacheIndexInMemory: false, ...config }; if (config.compression) { this.config.compression = { ...this.config.compression, ...config.compression }; } } /** * Compress vector data using specified compression method */ async compressVector(vector, segmentId) { const startTime = Date.now(); let compressedData; switch (this.config.compression.vectorCompression) { case CompressionType.QUANTIZATION: compressedData = await this.quantizeVector(vector, segmentId); break; case CompressionType.GZIP: const gzipBuffer = new Float32Array(vector).buffer; compressedData = await this.gzipCompress(gzipBuffer.slice(0)); break; case CompressionType.BROTLI: const brotliBuffer = new Float32Array(vector).buffer; compressedData = await this.brotliCompress(brotliBuffer.slice(0)); break; case CompressionType.HYBRID: // First quantize, then compress const quantized = await this.quantizeVector(vector, segmentId); compressedData = await this.gzipCompress(quantized); break; default: const defaultBuffer = new Float32Array(vector).buffer; compressedData = defaultBuffer.slice(0); break; } // Update compression statistics const originalSize = vector.length * 4; // 4 bytes per float32 this.compressionStats.originalSize += originalSize; this.compressionStats.compressedSize += compressedData.byteLength; this.compressionStats.decompressionTime += Date.now() - startTime; this.updateCompressionRatio(); return compressedData; } /** * Decompress vector data */ async decompressVector(compressedData, segmentId, originalDimension) { switch (this.config.compression.vectorCompression) { case CompressionType.QUANTIZATION: return this.dequantizeVector(compressedData, segmentId, originalDimension); case CompressionType.GZIP: const gzipDecompressed = await this.gzipDecompress(compressedData); return Array.from(new Float32Array(gzipDecompressed)); case CompressionType.BROTLI: const brotliDecompressed = await this.brotliDecompress(compressedData); return Array.from(new Float32Array(brotliDecompressed)); case CompressionType.HYBRID: const gzipStage = await this.gzipDecompress(compressedData); return this.dequantizeVector(gzipStage, segmentId, originalDimension); default: return Array.from(new Float32Array(compressedData)); } } /** * Scalar quantization of vectors to 8-bit integers */ async quantizeVector(vector, segmentId) { let codebook = this.quantizationCodebooks.get(segmentId); if (!codebook) { // Create codebook (min/max values for scaling) const min = Math.min(...vector); const max = Math.max(...vector); codebook = new Float32Array([min, max]); this.quantizationCodebooks.set(segmentId, codebook); } const [min, max] = codebook; const scale = (max - min) / 255; // 8-bit quantization const quantized = new Uint8Array(vector.length); for (let i = 0; i < vector.length; i++) { quantized[i] = Math.round((vector[i] - min) / scale); } // Store codebook with quantized data const result = new ArrayBuffer(quantized.byteLength + codebook.byteLength); const resultView = new Uint8Array(result); // First 8 bytes: codebook (min, max as float32) resultView.set(new Uint8Array(codebook.buffer), 0); // Remaining bytes: quantized vector resultView.set(quantized, codebook.byteLength); return result; } /** * Dequantize 8-bit vectors back to float32 */ dequantizeVector(quantizedData, segmentId, dimension) { const dataView = new Uint8Array(quantizedData); // Extract codebook (first 8 bytes) const codebookBytes = dataView.slice(0, 8); const codebook = new Float32Array(codebookBytes.buffer); const [min, max] = codebook; // Extract quantized vector const quantized = dataView.slice(8); const scale = (max - min) / 255; const result = []; for (let i = 0; i < dimension; i++) { result[i] = min + quantized[i] * scale; } return result; } /** * GZIP compression using browser/Node.js APIs */ async gzipCompress(data) { if (typeof CompressionStream !== 'undefined') { // Browser environment const stream = new CompressionStream('gzip'); const writer = stream.writable.getWriter(); const reader = stream.readable.getReader(); writer.write(new Uint8Array(data)); writer.close(); const chunks = []; let result = await reader.read(); while (!result.done) { chunks.push(result.value); result = await reader.read(); } // Combine chunks const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); const combined = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { combined.set(chunk, offset); offset += chunk.length; } return combined.buffer; } else { // Node.js environment - would use zlib console.warn('GZIP compression not available, returning original data'); return data; } } /** * GZIP decompression */ async gzipDecompress(compressedData) { if (typeof DecompressionStream !== 'undefined') { // Browser environment const stream = new DecompressionStream('gzip'); const writer = stream.writable.getWriter(); const reader = stream.readable.getReader(); writer.write(new Uint8Array(compressedData)); writer.close(); const chunks = []; let result = await reader.read(); while (!result.done) { chunks.push(result.value); result = await reader.read(); } // Combine chunks const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); const combined = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { combined.set(chunk, offset); offset += chunk.length; } return combined.buffer; } else { console.warn('GZIP decompression not available, returning original data'); return compressedData; } } /** * Brotli compression (placeholder - similar to GZIP) */ async brotliCompress(data) { // Would implement Brotli compression here console.warn('Brotli compression not implemented, falling back to GZIP'); return this.gzipCompress(data); } /** * Brotli decompression (placeholder) */ async brotliDecompress(compressedData) { console.warn('Brotli decompression not implemented, falling back to GZIP'); return this.gzipDecompress(compressedData); } /** * Create prebuilt index segments for faster loading */ async createPrebuiltSegments(nodes, outputPath) { const segments = []; const segmentSize = this.config.segmentSize; console.log(`Creating ${Math.ceil(nodes.length / segmentSize)} prebuilt segments`); for (let i = 0; i < nodes.length; i += segmentSize) { const segmentNodes = nodes.slice(i, i + segmentSize); const segmentId = `segment_${Math.floor(i / segmentSize)}`; const segment = { id: segmentId, nodeCount: segmentNodes.length, vectorDimension: segmentNodes[0]?.vector.length || 0, compression: this.config.compression.vectorCompression, localPath: `${outputPath}/${segmentId}.dat`, loadedInMemory: false, lastAccessed: 0 }; // Compress and serialize segment data const compressedData = await this.compressSegment(segmentNodes); // In a real implementation, you would write this to disk/S3 console.log(`Created segment ${segmentId} with ${compressedData.byteLength} bytes`); segments.push(segment); this.segments.set(segmentId, segment); } return segments; } /** * Compress an entire segment of nodes */ async compressSegment(nodes) { const serialized = JSON.stringify(nodes.map(node => ({ id: node.id, vector: node.vector, connections: this.serializeConnections(node.connections) }))); const encoder = new TextEncoder(); const data = encoder.encode(serialized); // Apply metadata compression switch (this.config.compression.metadataCompression) { case CompressionType.GZIP: return this.gzipCompress(data.buffer.slice(0)); case CompressionType.BROTLI: return this.brotliCompress(data.buffer.slice(0)); default: return data.buffer.slice(0); } } /** * Load a segment from storage with caching */ async loadSegment(segmentId) { const segment = this.segments.get(segmentId); if (!segment) { throw new Error(`Segment ${segmentId} not found`); } segment.lastAccessed = Date.now(); // Check if segment is already loaded in memory if (segment.loadedInMemory && this.memoryMappedBuffers.has(segmentId)) { return this.deserializeSegment(this.memoryMappedBuffers.get(segmentId)); } // Load from storage (S3, disk, etc.) const compressedData = await this.loadSegmentFromStorage(segment); // Cache in memory if configured if (this.config.cacheIndexInMemory) { this.memoryMappedBuffers.set(segmentId, compressedData); segment.loadedInMemory = true; } return this.deserializeSegment(compressedData); } /** * Load segment data from storage */ async loadSegmentFromStorage(segment) { // This would integrate with your S3 storage adapter // For now, return a placeholder console.log(`Loading segment ${segment.id} from storage`); return new ArrayBuffer(0); } /** * Deserialize and decompress segment data */ async deserializeSegment(compressedData) { // Decompress metadata let decompressed; switch (this.config.compression.metadataCompression) { case CompressionType.GZIP: decompressed = await this.gzipDecompress(compressedData); break; case CompressionType.BROTLI: decompressed = await this.brotliDecompress(compressedData); break; default: decompressed = compressedData; break; } // Parse JSON const decoder = new TextDecoder(); const jsonStr = decoder.decode(decompressed); const parsed = JSON.parse(jsonStr); // Reconstruct HNSWNoun objects return parsed.map((item) => ({ id: item.id, vector: item.vector, connections: this.deserializeConnections(item.connections) })); } /** * Serialize connections Map for storage */ serializeConnections(connections) { const result = {}; for (const [level, nodeIds] of connections.entries()) { result[level.toString()] = Array.from(nodeIds); } return result; } /** * Deserialize connections from storage format */ deserializeConnections(serialized) { const result = new Map(); for (const [levelStr, nodeIds] of Object.entries(serialized)) { result.set(parseInt(levelStr), new Set(nodeIds)); } return result; } /** * Prefetch segments based on access patterns */ async prefetchSegments(currentSegmentId) { const segment = this.segments.get(currentSegmentId); if (!segment) return; // Simple prefetching strategy - load adjacent segments const segmentNumber = parseInt(currentSegmentId.split('_')[1]); const toPrefetch = []; for (let i = 1; i <= this.config.prefetchSegments; i++) { const nextId = `segment_${segmentNumber + i}`; const prevId = `segment_${segmentNumber - i}`; if (this.segments.has(nextId) && !this.memoryMappedBuffers.has(nextId)) { toPrefetch.push(nextId); } if (this.segments.has(prevId) && !this.memoryMappedBuffers.has(prevId)) { toPrefetch.push(prevId); } } // Prefetch in background for (const segmentId of toPrefetch) { this.loadSegment(segmentId).catch(error => { console.warn(`Failed to prefetch segment ${segmentId}:`, error); }); } } /** * Update compression statistics */ updateCompressionRatio() { if (this.compressionStats.originalSize > 0) { this.compressionStats.compressionRatio = this.compressionStats.compressedSize / this.compressionStats.originalSize; } } /** * Get compression statistics */ getCompressionStats() { const memoryUsage = Array.from(this.memoryMappedBuffers.values()) .reduce((sum, buffer) => sum + buffer.byteLength, 0); return { ...this.compressionStats, segmentCount: this.segments.size, memoryUsage }; } /** * Cleanup memory-mapped buffers */ cleanup() { this.memoryMappedBuffers.clear(); this.quantizationCodebooks.clear(); // Mark all segments as not loaded for (const segment of this.segments.values()) { segment.loadedInMemory = false; } } } //# sourceMappingURL=readOnlyOptimizations.js.map