@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
JavaScript
"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