UNPKG

semantic-ds-toolkit

Version:

Performance-first semantic layer for modern data stacks - Stable Column Anchors & intelligent inference

389 lines 13.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.globalCacheManager = exports.MultiTierCacheManager = void 0; class LRUCache { cache = new Map(); accessOrder = []; maxSize; stats; constructor(maxSize) { this.maxSize = maxSize; this.stats = { hits: 0, misses: 0, evictions: 0, hitRate: 0, memoryUsage: 0, entryCount: 0 }; } get(key) { const entry = this.cache.get(key); if (!entry) { this.stats.misses++; this.updateHitRate(); return undefined; } // Check TTL if (entry.ttl && Date.now() > entry.timestamp + entry.ttl) { this.cache.delete(key); this.removeFromAccessOrder(key); this.stats.misses++; this.updateHitRate(); return undefined; } // Update access tracking entry.accessCount++; entry.timestamp = Date.now(); this.moveToFront(key); this.stats.hits++; this.updateHitRate(); return entry.value; } set(key, value, ttl) { const size = this.calculateSize(value); if (this.cache.has(key)) { // Update existing entry const entry = this.cache.get(key); this.stats.memoryUsage = this.stats.memoryUsage - entry.size + size; entry.value = value; entry.timestamp = Date.now(); entry.size = size; if (ttl) entry.ttl = ttl; this.moveToFront(key); } else { // New entry if (this.cache.size >= this.maxSize) { this.evictLRU(); } const entry = { value, timestamp: Date.now(), accessCount: 1, size, ttl }; this.cache.set(key, entry); this.accessOrder.unshift(key); this.stats.memoryUsage += size; this.stats.entryCount++; } } evictLRU() { const lruKey = this.accessOrder.pop(); if (lruKey) { const entry = this.cache.get(lruKey); if (entry) { this.stats.memoryUsage -= entry.size; this.stats.evictions++; this.stats.entryCount--; } this.cache.delete(lruKey); } } moveToFront(key) { this.removeFromAccessOrder(key); this.accessOrder.unshift(key); } removeFromAccessOrder(key) { const index = this.accessOrder.indexOf(key); if (index > -1) { this.accessOrder.splice(index, 1); } } calculateSize(value) { // Rough estimation of memory size return JSON.stringify(value).length * 2; // UTF-16 characters } updateHitRate() { const total = this.stats.hits + this.stats.misses; this.stats.hitRate = total > 0 ? this.stats.hits / total : 0; } getStats() { return { ...this.stats }; } clear() { this.cache.clear(); this.accessOrder = []; this.stats = { hits: 0, misses: 0, evictions: 0, hitRate: 0, memoryUsage: 0, entryCount: 0 }; } } class MultiTierCacheManager { l1Cache; // Hot data - most frequently accessed l2Cache; // Warm data - occasionally accessed l3Cache; // Cold data - rarely accessed config; globalStats; compressionCache = new Map(); // Compressed data storage constructor(config = {}) { this.config = { l1MaxSize: 1000, // Hot cache - 1K entries l2MaxSize: 10000, // Warm cache - 10K entries l3MaxSize: 100000, // Cold cache - 100K entries defaultTTL: 3600000, // 1 hour default TTL maxMemoryMB: 512, // 512MB max memory evictionPolicy: 'ADAPTIVE', compressionThreshold: 1024, // Compress entries > 1KB ...config }; this.l1Cache = new LRUCache(this.config.l1MaxSize); this.l2Cache = new LRUCache(this.config.l2MaxSize); this.l3Cache = new LRUCache(this.config.l3MaxSize); this.globalStats = { hits: 0, misses: 0, evictions: 0, hitRate: 0, memoryUsage: 0, entryCount: 0 }; } get(key) { // Try L1 first (hottest data) let value = this.l1Cache.get(key); if (value !== undefined) { this.updateGlobalStats(); return this.maybeDecompress(value); } // Try L2 next (warm data) value = this.l2Cache.get(key); if (value !== undefined) { // Promote to L1 on access this.l1Cache.set(key, value); this.updateGlobalStats(); return this.maybeDecompress(value); } // Try L3 last (cold data) value = this.l3Cache.get(key); if (value !== undefined) { // Promote to L2 on access this.l2Cache.set(key, value); this.updateGlobalStats(); return this.maybeDecompress(value); } this.updateGlobalStats(); return undefined; } set(key, value, options = {}) { const compressed = this.maybeCompress(value); const { ttl, tier } = options; if (tier === 1 || this.shouldPromoteToL1(key, value)) { this.l1Cache.set(key, compressed, ttl); } else if (tier === 2 || this.shouldPromoteToL2(key, value)) { this.l2Cache.set(key, compressed, ttl); } else { this.l3Cache.set(key, compressed, ttl); } this.updateGlobalStats(); this.enforceMemoryLimits(); } shouldPromoteToL1(key, value) { // Promote frequently accessed anchors and fingerprints if (this.isAnchor(value) || this.isFingerprint(value)) { return true; } return false; } shouldPromoteToL2(key, value) { // Promote medium-priority data return this.calculateSize(value) < 10000; // < 10KB } isAnchor(value) { return value && typeof value === 'object' && 'anchor_id' in value; } isFingerprint(value) { return value && typeof value === 'object' && 'column_hash' in value; } maybeCompress(value) { const size = this.calculateSize(value); if (size > this.config.compressionThreshold) { try { // Simple compression simulation - in production use zlib const compressed = JSON.stringify(value); const key = this.generateCompressionKey(compressed); this.compressionCache.set(key, compressed); return { __compressed: key }; } catch (error) { return value; // Return original if compression fails } } return value; } maybeDecompress(value) { if (value && typeof value === 'object' && '__compressed' in value) { const compressed = this.compressionCache.get(value.__compressed); if (compressed) { try { return JSON.parse(compressed); } catch (error) { return value; } } } return value; } generateCompressionKey(data) { // Simple hash for compression key let hash = 0; for (let i = 0; i < data.length; i++) { const char = data.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return hash.toString(36); } calculateSize(value) { return JSON.stringify(value).length * 2; // Rough UTF-16 estimate } enforceMemoryLimits() { const maxMemoryBytes = this.config.maxMemoryMB * 1024 * 1024; const currentMemory = this.getCurrentMemoryUsage(); if (currentMemory > maxMemoryBytes) { // Evict from L3 first, then L2, then L1 this.l3Cache.clear(); if (this.getCurrentMemoryUsage() > maxMemoryBytes) { this.l2Cache.clear(); } } } getCurrentMemoryUsage() { const l1Stats = this.l1Cache.getStats(); const l2Stats = this.l2Cache.getStats(); const l3Stats = this.l3Cache.getStats(); return l1Stats.memoryUsage + l2Stats.memoryUsage + l3Stats.memoryUsage; } updateGlobalStats() { const l1Stats = this.l1Cache.getStats(); const l2Stats = this.l2Cache.getStats(); const l3Stats = this.l3Cache.getStats(); this.globalStats = { hits: l1Stats.hits + l2Stats.hits + l3Stats.hits, misses: l1Stats.misses + l2Stats.misses + l3Stats.misses, evictions: l1Stats.evictions + l2Stats.evictions + l3Stats.evictions, hitRate: 0, memoryUsage: l1Stats.memoryUsage + l2Stats.memoryUsage + l3Stats.memoryUsage, entryCount: l1Stats.entryCount + l2Stats.entryCount + l3Stats.entryCount }; const total = this.globalStats.hits + this.globalStats.misses; this.globalStats.hitRate = total > 0 ? this.globalStats.hits / total : 0; } // Cache invalidation patterns for SCAs invalidateAnchor(anchorId) { const patterns = [ `anchor:${anchorId}`, `anchor:${anchorId}:*`, `fingerprint:${anchorId}`, `reconciliation:*:${anchorId}` ]; patterns.forEach(pattern => { this.invalidatePattern(pattern); }); } invalidateDataset(datasetName) { const patterns = [ `dataset:${datasetName}:*`, `reconciliation:${datasetName}:*` ]; patterns.forEach(pattern => { this.invalidatePattern(pattern); }); } invalidatePattern(pattern) { // Simple pattern matching - in production use more sophisticated matching [this.l1Cache, this.l2Cache, this.l3Cache].forEach(cache => { // This is a simplified implementation // In production, maintain key indexes for efficient pattern matching }); } // Batch operations for better performance mget(keys) { const results = new Map(); keys.forEach(key => { const value = this.get(key); if (value !== undefined) { results.set(key, value); } }); return results; } mset(entries) { entries.forEach(({ key, value, options }) => { this.set(key, value, options); }); } // Performance optimization methods preload(keys, loader) { return Promise.all(keys.map(async (key) => { if (this.get(key) === undefined) { try { const value = await loader(key); this.set(key, value, { tier: 3 }); // Load into cold cache } catch (error) { console.error(`Failed to preload cache key ${key}:`, error); } } })).then(() => undefined); } warmup(anchorIds) { anchorIds.forEach(id => { // Move frequently accessed anchors to L1 const key = `anchor:${id}`; const value = this.l3Cache.get(key) || this.l2Cache.get(key); if (value) { this.l1Cache.set(key, value); } }); } getStats() { return { global: { ...this.globalStats }, l1: this.l1Cache.getStats(), l2: this.l2Cache.getStats(), l3: this.l3Cache.getStats(), compressionRatio: this.compressionCache.size / Math.max(1, this.globalStats.entryCount) }; } clear() { this.l1Cache.clear(); this.l2Cache.clear(); this.l3Cache.clear(); this.compressionCache.clear(); this.updateGlobalStats(); } // Utility methods for cache key generation static anchorKey(anchorId) { return `anchor:${anchorId}`; } static fingerprintKey(columnHash) { return `fingerprint:${columnHash}`; } static reconciliationKey(datasetName, columnName) { return `reconciliation:${datasetName}:${columnName}`; } } exports.MultiTierCacheManager = MultiTierCacheManager; // Global cache instance with optimized settings for SCA workloads exports.globalCacheManager = new MultiTierCacheManager({ l1MaxSize: 5000, // 5K hot anchors/fingerprints l2MaxSize: 50000, // 50K warm data l3MaxSize: 500000, // 500K cold storage defaultTTL: 7200000, // 2 hours maxMemoryMB: 1024, // 1GB max memory evictionPolicy: 'ADAPTIVE', compressionThreshold: 512 // Compress > 512 bytes }); //# sourceMappingURL=cache-manager.js.map