UNPKG

@ahmedhegazee/nestjs-telescope

Version:

Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling

624 lines 23.2 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var AdvancedCachingService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.AdvancedCachingService = void 0; const common_1 = require("@nestjs/common"); const rxjs_1 = require("rxjs"); const common_2 = require("@nestjs/common"); let AdvancedCachingService = AdvancedCachingService_1 = class AdvancedCachingService { constructor(telescopeConfig) { this.telescopeConfig = telescopeConfig; this.logger = new common_1.Logger(AdvancedCachingService_1.name); this.l1Cache = new Map(); this.l2Cache = new Map(); this.l3Cache = new Map(); this.operationSubject = new rxjs_1.Subject(); this.metricsSubject = new rxjs_1.Subject(); this.accessOrder = []; this.tagIndex = new Map(); this.monitoringInterval = null; const defaultConfig = this.getDefaultCacheConfig(); if (this.telescopeConfig.caching) { this.config = { ...defaultConfig, enabled: this.telescopeConfig.caching.enabled ?? defaultConfig.enabled, tiers: { l1: { ...defaultConfig.tiers.l1, ...this.telescopeConfig.caching.tiers?.l1 }, l2: { ...defaultConfig.tiers.l2, ...this.telescopeConfig.caching.tiers?.l2 }, l3: { ...defaultConfig.tiers.l3, ...this.telescopeConfig.caching.tiers?.l3 }, }, strategies: { ...defaultConfig.strategies, ...this.telescopeConfig.caching.policies }, monitoring: { ...defaultConfig.monitoring }, }; } else { this.config = defaultConfig; } this.metrics = this.initializeMetrics(); } async onModuleInit() { if (!this.config.enabled) { this.logger.log("Advanced caching disabled"); return; } await this.initializeCaches(); this.startMonitoring(); this.logger.log("Advanced caching service initialized"); } getDefaultCacheConfig() { return { enabled: true, tiers: { l1: { enabled: true, type: "memory", maxSize: 100, ttl: 300000, maxEntries: 10000, }, l2: { enabled: true, type: "redis", host: "localhost", port: 6379, ttl: 3600000, maxSize: 1024, compression: true, }, l3: { enabled: false, type: "database", ttl: 86400000, maxSize: 10240, }, }, strategies: { writePolicy: "write-through", readPolicy: "read-through", evictionPolicy: "lru", compression: true, encryption: false, }, monitoring: { enabled: true, metricsInterval: 60000, hitRateThreshold: 0.8, sizeThreshold: 0.9, }, }; } initializeMetrics() { return { hits: 0, misses: 0, hitRate: 0, totalSize: 0, entryCount: 0, evictions: 0, compressions: 0, tierMetrics: { l1: { hits: 0, misses: 0, hitRate: 0, size: 0, entryCount: 0, evictions: 0, }, l2: { hits: 0, misses: 0, hitRate: 0, size: 0, entryCount: 0, evictions: 0, }, l3: { hits: 0, misses: 0, hitRate: 0, size: 0, entryCount: 0, evictions: 0, }, }, performance: { averageAccessTime: 0, averageWriteTime: 0, compressionRatio: 1, }, }; } async initializeCaches() { if (this.config.tiers.l1.enabled) { this.logger.log("L1 cache initialized"); } if (this.config.tiers.l2.enabled) { await this.initializeL2Cache(); } if (this.config.tiers.l3.enabled) { await this.initializeL3Cache(); } } async initializeL2Cache() { this.logger.log("L2 cache initialized"); } async initializeL3Cache() { this.logger.log("L3 cache initialized"); } startMonitoring() { if (!this.config.monitoring.enabled) return; this.monitoringInterval = setInterval(() => { this.updateMetrics(); this.checkThresholds(); this.metricsSubject.next(this.metrics); }, this.config.monitoring.metricsInterval); } async get(key, options = {}) { const startTime = Date.now(); const tier = options.tier || "l1"; try { let value = null; let cacheHit = false; if (tier === "l1" || tier === "l2" || tier === "l3") { value = await this.getFromL1(key); if (value !== null) { cacheHit = true; this.recordOperation("get", key, "l1", Date.now() - startTime, true); return value; } } if (tier === "l2" || tier === "l3") { value = await this.getFromL2(key); if (value !== null) { cacheHit = true; if (this.config.strategies.readPolicy === "read-through") { await this.setInL1(key, value, options); } this.recordOperation("get", key, "l2", Date.now() - startTime, true); return value; } } if (tier === "l3") { value = await this.getFromL3(key); if (value !== null) { cacheHit = true; if (this.config.strategies.readPolicy === "read-through") { await this.setInL2(key, value, options); await this.setInL1(key, value, options); } this.recordOperation("get", key, "l3", Date.now() - startTime, true); return value; } } this.recordOperation("get", key, tier, Date.now() - startTime, false); return null; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(`Cache get error for key ${key}: ${errorMessage}`); this.recordOperation("get", key, tier, Date.now() - startTime, false, errorMessage); return null; } } async set(key, value, options = {}) { const startTime = Date.now(); const tier = options.tier || "l1"; const ttl = options.ttl || this.config.tiers.l1.ttl; try { const entry = { key, value, timestamp: new Date(), ttl, accessCount: 0, lastAccessed: new Date(), size: this.calculateSize(value), tags: options.tags || [], priority: options.priority || "medium", version: options.version || "1.0", metadata: {}, }; switch (this.config.strategies.writePolicy) { case "write-through": await this.writeThrough(key, entry, tier); break; case "write-back": await this.writeBack(key, entry, tier); break; case "write-around": await this.writeAround(key, entry, tier); break; } this.recordOperation("set", key, tier, Date.now() - startTime, true); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(`Cache set error for key ${key}: ${errorMessage}`); this.recordOperation("set", key, tier, Date.now() - startTime, false, errorMessage); } } async delete(key, options = {}) { const startTime = Date.now(); const tier = options.tier || "l1"; try { let deleted = false; if (tier === "l1" || tier === "l2" || tier === "l3") { deleted = (await this.deleteFromL1(key)) || deleted; } if (tier === "l2" || tier === "l3") { deleted = (await this.deleteFromL2(key)) || deleted; } if (tier === "l3") { deleted = (await this.deleteFromL3(key)) || deleted; } this.recordOperation("delete", key, tier, Date.now() - startTime, deleted); return deleted; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(`Cache delete error for key ${key}: ${errorMessage}`); this.recordOperation("delete", key, tier, Date.now() - startTime, false, errorMessage); return false; } } async invalidateByTags(tags) { let invalidatedCount = 0; for (const tag of tags) { const keys = this.tagIndex.get(tag); if (keys) { for (const key of keys) { await this.delete(key); invalidatedCount++; } this.tagIndex.delete(tag); } } this.logger.log(`Invalidated ${invalidatedCount} entries by tags: ${tags.join(", ")}`); return invalidatedCount; } async clear(tier) { const startTime = Date.now(); try { if (!tier || tier === "l1") { this.l1Cache.clear(); this.accessOrder.length = 0; this.tagIndex.clear(); } if (!tier || tier === "l2") { await this.clearL2Cache(); } if (!tier || tier === "l3") { await this.clearL3Cache(); } this.recordOperation("clear", "all", tier || "l1", Date.now() - startTime, true); this.logger.log(`Cache cleared for tier: ${tier || "all"}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(`Cache clear error: ${errorMessage}`); this.recordOperation("clear", "all", tier || "l1", Date.now() - startTime, false, errorMessage); } } async getFromL1(key) { const entry = this.l1Cache.get(key); if (!entry) { this.metrics.tierMetrics.l1.misses++; return null; } if (this.isExpired(entry)) { this.l1Cache.delete(key); this.removeFromAccessOrder(key); this.metrics.tierMetrics.l1.misses++; return null; } entry.accessCount++; entry.lastAccessed = new Date(); this.updateAccessOrder(key); this.metrics.tierMetrics.l1.hits++; return entry.value; } async getFromL2(key) { this.metrics.tierMetrics.l2.misses++; return null; } async getFromL3(key) { this.metrics.tierMetrics.l3.misses++; return null; } async setInL1(key, value, options) { const entry = { key, value, timestamp: new Date(), ttl: options.ttl || this.config.tiers.l1.ttl, accessCount: 0, lastAccessed: new Date(), size: this.calculateSize(value), tags: options.tags || [], priority: options.priority || "medium", version: options.version || "1.0", metadata: {}, }; await this.ensureL1Capacity(entry.size); this.l1Cache.set(key, entry); this.updateAccessOrder(key); this.updateTagIndex(key, entry.tags); this.metrics.tierMetrics.l1.entryCount++; } async setInL2(key, value, options) { this.metrics.tierMetrics.l2.entryCount++; } async setInL3(key, value, options) { this.metrics.tierMetrics.l3.entryCount++; } async deleteFromL1(key) { const deleted = this.l1Cache.delete(key); if (deleted) { this.removeFromAccessOrder(key); this.metrics.tierMetrics.l1.entryCount--; } return deleted; } async deleteFromL2(key) { return false; } async deleteFromL3(key) { return false; } async clearL2Cache() { this.metrics.tierMetrics.l2.entryCount = 0; } async clearL3Cache() { this.metrics.tierMetrics.l3.entryCount = 0; } async writeThrough(key, entry, tier) { if (tier === "l1" || tier === "l2" || tier === "l3") { await this.setInL1(key, entry.value, { ttl: entry.ttl, tags: entry.tags, }); } if (tier === "l2" || tier === "l3") { await this.setInL2(key, entry.value, { ttl: entry.ttl, tags: entry.tags, }); } if (tier === "l3") { await this.setInL3(key, entry.value, { ttl: entry.ttl, tags: entry.tags, }); } } async writeBack(key, entry, tier) { await this.setInL1(key, entry.value, { ttl: entry.ttl, tags: entry.tags }); setTimeout(async () => { if (tier === "l2" || tier === "l3") { await this.setInL2(key, entry.value, { ttl: entry.ttl, tags: entry.tags, }); } if (tier === "l3") { await this.setInL3(key, entry.value, { ttl: entry.ttl, tags: entry.tags, }); } }, 1000); } async writeAround(key, entry, tier) { if (tier === "l2" || tier === "l3") { await this.setInL2(key, entry.value, { ttl: entry.ttl, tags: entry.tags, }); } if (tier === "l3") { await this.setInL3(key, entry.value, { ttl: entry.ttl, tags: entry.tags, }); } } async ensureL1Capacity(newEntrySize) { const maxSize = this.config.tiers.l1.maxSize * 1024 * 1024; const maxEntries = this.config.tiers.l1.maxEntries; let currentSize = this.calculateL1Size(); let currentEntries = this.l1Cache.size; while ((currentSize + newEntrySize > maxSize || currentEntries >= maxEntries) && this.l1Cache.size > 0) { await this.evictFromL1(); currentSize = this.calculateL1Size(); currentEntries = this.l1Cache.size; } } async evictFromL1() { let keyToEvict = null; switch (this.config.strategies.evictionPolicy) { case "lru": keyToEvict = this.accessOrder[0] || null; break; case "lfu": keyToEvict = this.findLeastFrequentlyUsed(); break; case "fifo": keyToEvict = this.accessOrder[0] || null; break; case "random": const keys = Array.from(this.l1Cache.keys()); keyToEvict = keys[Math.floor(Math.random() * keys.length)] || null; break; } if (keyToEvict) { await this.deleteFromL1(keyToEvict); this.metrics.tierMetrics.l1.evictions++; this.metrics.evictions++; } } findLeastFrequentlyUsed() { let minAccessCount = Infinity; let leastUsedKey = null; for (const [key, entry] of this.l1Cache.entries()) { if (entry.accessCount < minAccessCount) { minAccessCount = entry.accessCount; leastUsedKey = key; } } return leastUsedKey; } isExpired(entry) { const now = new Date(); const expiryTime = new Date(entry.timestamp.getTime() + entry.ttl); return now > expiryTime; } calculateSize(value) { return JSON.stringify(value).length; } calculateL1Size() { let totalSize = 0; for (const entry of this.l1Cache.values()) { totalSize += entry.size; } return totalSize; } updateAccessOrder(key) { this.removeFromAccessOrder(key); this.accessOrder.push(key); } removeFromAccessOrder(key) { const index = this.accessOrder.indexOf(key); if (index > -1) { this.accessOrder.splice(index, 1); } } updateTagIndex(key, tags) { for (const tag of tags) { if (!this.tagIndex.has(tag)) { this.tagIndex.set(tag, new Set()); } this.tagIndex.get(tag).add(key); } } recordOperation(type, key, tier, duration, success, error) { const operation = { type: type, key, tier: tier, timestamp: new Date(), duration, success, error, }; this.operationSubject.next(operation); } updateMetrics() { const totalHits = this.metrics.tierMetrics.l1.hits + this.metrics.tierMetrics.l2.hits + this.metrics.tierMetrics.l3.hits; const totalMisses = this.metrics.tierMetrics.l1.misses + this.metrics.tierMetrics.l2.misses + this.metrics.tierMetrics.l3.misses; const totalRequests = totalHits + totalMisses; this.metrics.hits = totalHits; this.metrics.misses = totalMisses; this.metrics.hitRate = totalRequests > 0 ? totalHits / totalRequests : 0; const l1Total = this.metrics.tierMetrics.l1.hits + this.metrics.tierMetrics.l1.misses; this.metrics.tierMetrics.l1.hitRate = l1Total > 0 ? this.metrics.tierMetrics.l1.hits / l1Total : 0; const l2Total = this.metrics.tierMetrics.l2.hits + this.metrics.tierMetrics.l2.misses; this.metrics.tierMetrics.l2.hitRate = l2Total > 0 ? this.metrics.tierMetrics.l2.hits / l2Total : 0; const l3Total = this.metrics.tierMetrics.l3.hits + this.metrics.tierMetrics.l3.misses; this.metrics.tierMetrics.l3.hitRate = l3Total > 0 ? this.metrics.tierMetrics.l3.hits / l3Total : 0; this.metrics.tierMetrics.l1.size = this.calculateL1Size(); this.metrics.tierMetrics.l1.entryCount = this.l1Cache.size; this.metrics.totalSize = this.metrics.tierMetrics.l1.size + this.metrics.tierMetrics.l2.size + this.metrics.tierMetrics.l3.size; this.metrics.entryCount = this.metrics.tierMetrics.l1.entryCount + this.metrics.tierMetrics.l2.entryCount + this.metrics.tierMetrics.l3.entryCount; } checkThresholds() { if (this.metrics.hitRate < this.config.monitoring.hitRateThreshold) { this.logger.warn(`Cache hit rate (${this.metrics.hitRate.toFixed(2)}) below threshold (${this.config.monitoring.hitRateThreshold})`); } const maxSize = this.config.tiers.l1.maxSize * 1024 * 1024; const usageRatio = this.metrics.tierMetrics.l1.size / maxSize; if (usageRatio > this.config.monitoring.sizeThreshold) { this.logger.warn(`L1 cache usage (${(usageRatio * 100).toFixed(1)}%) above threshold (${this.config.monitoring.sizeThreshold * 100}%)`); } } getMetrics() { return { ...this.metrics }; } getOperations() { return this.operationSubject.asObservable(); } getMetricsUpdates() { return this.metricsSubject.asObservable(); } async warmup(keys) { this.logger.log(`Warming up cache with ${keys.length} keys`); for (const key of keys) { try { await this.get(key); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.warn(`Failed to warm up key ${key}: ${errorMessage}`); } } } async optimize() { this.logger.log("Starting cache optimization"); for (const [key, entry] of this.l1Cache.entries()) { if (this.isExpired(entry)) { await this.deleteFromL1(key); } } if (this.config.strategies.compression) { await this.compressLargeEntries(); } this.logger.log("Cache optimization completed"); } async compressLargeEntries() { this.metrics.compressions++; } async shutdown() { if (this.monitoringInterval) { clearInterval(this.monitoringInterval); } await this.persistWriteBackData(); this.logger.log("Advanced caching service shutdown"); } async persistWriteBackData() { this.logger.debug("Persisting write-back data"); } }; exports.AdvancedCachingService = AdvancedCachingService; exports.AdvancedCachingService = AdvancedCachingService = AdvancedCachingService_1 = __decorate([ (0, common_1.Injectable)(), __param(0, (0, common_2.Inject)("TELESCOPE_CONFIG")), __metadata("design:paramtypes", [Object]) ], AdvancedCachingService); //# sourceMappingURL=advanced-caching.service.js.map