/**
* CONTEXT: Intelligent cache warming and preloading system for MCP server optimization
* REASON: Proactively load frequently accessed data to reduce cache misses and improve performance
* CHANGE: Smart cache warming strategies based on usage patterns and predictive analytics
* PREVENTION: Cache misses, slow response times, inefficient resource utilization
*/
import { EventEmitter } from 'events';
import { logger } from '../utils/logger.js';
import fs from 'fs/promises';
import path from 'path';
export class CacheWarmer extends EventEmitter {
constructor(config = {}) {
super();
this.config = {
// Warming strategies
strategies: {
predictiveWarming: config.strategies?.predictiveWarming !== false,
patternBasedWarming: config.strategies?.patternBasedWarming !== false,
timeBasedWarming: config.strategies?.timeBasedWarming !== false,
dependencyWarming: config.strategies?.dependencyWarming !== false,
adaptiveWarming: config.strategies?.adaptiveWarming !== false,
...config.strategies
},
// Warming intervals and timing
warmingInterval: config.warmingInterval || 300000, // 5 minutes
preloadInterval: config.preloadInterval || 60000, // 1 minute
analysisInterval: config.analysisInterval || 120000, // 2 minutes
// Pattern analysis settings
patternAnalysis: {
minRequests: config.patternAnalysis?.minRequests || 5,
minFrequency: config.patternAnalysis?.minFrequency || 0.1, // 10%
timeWindow: config.patternAnalysis?.timeWindow || 3600000, // 1 hour
maxPatterns: config.patternAnalysis?.maxPatterns || 1000,
...config.patternAnalysis
},
// Predictive settings
predictive: {
enabled: config.predictive?.enabled !== false,
lookAheadTime: config.predictive?.lookAheadTime || 1800000, // 30 minutes
confidenceThreshold: config.predictive?.confidenceThreshold || 0.7,
maxPredictions: config.predictive?.maxPredictions || 100,
...config.predictive
},
// Resource limits
resources: {
maxWarmingConcurrency: config.resources?.maxWarmingConcurrency || 5,
maxMemoryUsage: config.resources?.maxMemoryUsage || 100 * 1024 * 1024, // 100MB
maxWarmingTime: config.resources?.maxWarmingTime || 30000, // 30 seconds
...config.resources
},
// Priority settings
priority: {
highPriorityThreshold: config.priority?.highPriorityThreshold || 0.8,
mediumPriorityThreshold: config.priority?.mediumPriorityThreshold || 0.5,
lowPriorityThreshold: config.priority?.lowPriorityThreshold || 0.2,
...config.priority
}
};
// Cache registries
this.cacheProviders = new Map();
this.warmingQueues = new Map(['high', 'medium', 'low'].map(p => [p, []]));
// Pattern tracking
this.accessPatterns = new Map();
this.requestHistory = [];
this.timeBasedPatterns = new Map();
this.dependencyGraph = new Map();
// Predictive analytics
this.predictor = new CachePredictionEngine(this);
this.patternAnalyzer = new AccessPatternAnalyzer(this);
this.dependencyTracker = new DependencyTracker(this);
// Warming state
this.warmingState = {
active: false,
inProgress: new Set(),
completed: new Set(),
failed: new Set(),
lastWarming: null,
currentPriority: 'medium'
};
// Performance metrics
this.metrics = {
warming: {
total: 0,
successful: 0,
failed: 0,
averageTime: 0,
totalTime: 0,
byPriority: new Map(),
byStrategy: new Map()
},
patterns: {
detected: 0,
predicted: 0,
accuracy: 0,
coverage: 0
},
cache: {
preloadedItems: 0,
hitRateImprovement: 0,
missReduction: 0,
responseTtimeImprovement: 0
},
resources: {
memoryUsed: 0,
cpuTime: 0,
concurrentWarming: 0
}
};
// Monitoring intervals
this.monitoringIntervals = [];
this.initialized = false;
}
/**
* Initialize cache warmer
*/
async initialize() {
if (this.initialized) return;
try {
logger.info('Initializing cache warmer', {
strategies: this.config.strategies,
intervals: {
warming: this.config.warmingInterval,
preload: this.config.preloadInterval,
analysis: this.config.analysisInterval
}
});
// Initialize sub-systems
await this.predictor.initialize();
await this.patternAnalyzer.initialize();
await this.dependencyTracker.initialize();
// Start monitoring and warming processes
this.startMonitoring();
this.initialized = true;
this.emit('initialized');
logger.info('Cache warmer initialized successfully');
} catch (error) {
logger.error('Failed to initialize cache warmer:', error);
throw error;
}
}
/**
* Register cache provider for warming
*/
registerCacheProvider(name, provider, config = {}) {
const providerConfig = {
name: name,
provider: provider,
warmingEnabled: config.warmingEnabled !== false,
preloadEnabled: config.preloadEnabled !== false,
priority: config.priority || 'medium',
warmingMethods: config.warmingMethods || ['get', 'preload'],
dependencies: config.dependencies || [],
keyPatterns: config.keyPatterns || [],
...config
};
this.cacheProviders.set(name, providerConfig);
// Setup event listeners for cache provider
if (provider.on) {
provider.on('get', (event) => this.trackCacheAccess(name, event));
provider.on('miss', (event) => this.trackCacheMiss(name, event));
provider.on('set', (event) => this.trackCacheSet(name, event));
}
logger.debug(`Cache provider registered: ${name}`, providerConfig);
this.emit('providerRegistered', { name, config: providerConfig });
}
/**
* Unregister cache provider
*/
unregisterCacheProvider(name) {
if (this.cacheProviders.has(name)) {
this.cacheProviders.delete(name);
logger.debug(`Cache provider unregistered: ${name}`);
this.emit('providerUnregistered', { name });
}
}
/**
* Start monitoring and warming processes
*/
startMonitoring() {
// Pattern analysis
const patternAnalysisInterval = setInterval(() => {
this.patternAnalyzer.analyzePatterns().catch(error => {
logger.error('Pattern analysis failed:', error);
});
}, this.config.analysisInterval);
// Predictive warming
const predictiveWarmingInterval = setInterval(() => {
if (this.config.strategies.predictiveWarming) {
this.runPredictiveWarming().catch(error => {
logger.error('Predictive warming failed:', error);
});
}
}, this.config.warmingInterval);
// Time-based warming
const timeBasedWarmingInterval = setInterval(() => {
if (this.config.strategies.timeBasedWarming) {
this.runTimeBasedWarming().catch(error => {
logger.error('Time-based warming failed:', error);
});
}
}, this.config.preloadInterval);
// Pattern-based warming
const patternBasedWarmingInterval = setInterval(() => {
if (this.config.strategies.patternBasedWarming) {
this.runPatternBasedWarming().catch(error => {
logger.error('Pattern-based warming failed:', error);
});
}
}, this.config.warmingInterval);
// Dependency warming
const dependencyWarmingInterval = setInterval(() => {
if (this.config.strategies.dependencyWarming) {
this.runDependencyWarming().catch(error => {
logger.error('Dependency warming failed:', error);
});
}
}, this.config.warmingInterval);
// Queue processing
const queueProcessingInterval = setInterval(() => {
this.processWarmingQueues().catch(error => {
logger.error('Queue processing failed:', error);
});
}, 5000); // Process every 5 seconds
this.monitoringIntervals.push(
patternAnalysisInterval,
predictiveWarmingInterval,
timeBasedWarmingInterval,
patternBasedWarmingInterval,
dependencyWarmingInterval,
queueProcessingInterval
);
logger.debug('Cache warming monitoring started');
}
/**
* Track cache access for pattern analysis
*/
trackCacheAccess(providerName, event) {
const access = {
provider: providerName,
key: event.key,
timestamp: Date.now(),
hit: event.hit || false,
source: event.source || 'unknown',
metadata: event.metadata || {}
};
// Add to request history
this.requestHistory.push(access);
// Limit history size
if (this.requestHistory.length > 10000) {
this.requestHistory.shift();
}
// Update access patterns
this.updateAccessPatterns(access);
// Track dependencies
this.dependencyTracker.trackAccess(access);
this.emit('cacheAccess', access);
}
/**
* Track cache miss for warming prioritization
*/
trackCacheMiss(providerName, event) {
const miss = {
provider: providerName,
key: event.key,
timestamp: Date.now(),
reason: event.reason || 'not_found',
cost: event.cost || 1,
metadata: event.metadata || {}
};
// Prioritize warming for missed keys
this.prioritizeWarming(miss);
this.emit('cacheMiss', miss);
}
/**
* Track cache set operations
*/
trackCacheSet(providerName, event) {
const set = {
provider: providerName,
key: event.key,
timestamp: Date.now(),
size: event.size || 0,
ttl: event.ttl || 0,
metadata: event.metadata || {}
};
this.emit('cacheSet', set);
}
/**
* Update access patterns
*/
updateAccessPatterns(access) {
const key = access.key;
const provider = access.provider;
const patternKey = `${provider}:${key}`;
if (!this.accessPatterns.has(patternKey)) {
this.accessPatterns.set(patternKey, {
key: key,
provider: provider,
count: 0,
firstAccess: access.timestamp,
lastAccess: access.timestamp,
frequency: 0,
timePattern: [],
hitRate: 0,
hits: 0,
totalRequests: 0
});
}
const pattern = this.accessPatterns.get(patternKey);
pattern.count++;
pattern.lastAccess = access.timestamp;
pattern.timePattern.push(access.timestamp);
pattern.totalRequests++;
if (access.hit) {
pattern.hits++;
}
pattern.hitRate = pattern.hits / pattern.totalRequests;
// Calculate frequency (requests per hour)
const timeSpan = pattern.lastAccess - pattern.firstAccess;
pattern.frequency = timeSpan > 0 ? (pattern.count / timeSpan) * 3600000 : 0;
// Keep only recent time pattern data
if (pattern.timePattern.length > 100) {
pattern.timePattern.shift();
}
}
/**
* Prioritize warming based on cache miss
*/
prioritizeWarming(miss) {
// Calculate priority based on miss frequency and cost
const patternKey = `${miss.provider}:${miss.key}`;
const pattern = this.accessPatterns.get(patternKey);
let priority = 'low';
if (pattern) {
const missRate = 1 - pattern.hitRate;
const frequency = pattern.frequency;
const cost = miss.cost;
const score = (missRate * 0.4) + (frequency * 0.4) + (cost * 0.2);
if (score > this.config.priority.highPriorityThreshold) {
priority = 'high';
} else if (score > this.config.priority.mediumPriorityThreshold) {
priority = 'medium';
}
}
// Add to warming queue
this.addToWarmingQueue(miss.provider, miss.key, priority, {
reason: 'cache_miss',
missData: miss
});
}
/**
* Add item to warming queue
*/
addToWarmingQueue(provider, key, priority = 'medium', metadata = {}) {
const warmingItem = {
provider: provider,
key: key,
priority: priority,
timestamp: Date.now(),
attempts: 0,
metadata: metadata
};
const queue = this.warmingQueues.get(priority);
if (queue) {
// Check if item is already in queue
const exists = queue.some(item =>
item.provider === provider && item.key === key
);
if (!exists) {
queue.push(warmingItem);
this.emit('warmingQueued', warmingItem);
}
}
}
/**
* Process warming queues
*/
async processWarmingQueues() {
if (this.warmingState.active) return;
const maxConcurrency = this.config.resources.maxWarmingConcurrency;
if (this.warmingState.inProgress.size >= maxConcurrency) return;
// Process high priority queue first
for (const priority of ['high', 'medium', 'low']) {
const queue = this.warmingQueues.get(priority);
while (queue.length > 0 && this.warmingState.inProgress.size < maxConcurrency) {
const item = queue.shift();
await this.warmCacheItem(item);
}
}
}
/**
* Warm individual cache item
*/
async warmCacheItem(item) {
const warmingId = `${item.provider}:${item.key}:${Date.now()}`;
try {
this.warmingState.inProgress.add(warmingId);
this.metrics.resources.concurrentWarming = this.warmingState.inProgress.size;
const startTime = Date.now();
const provider = this.cacheProviders.get(item.provider);
if (!provider || !provider.warmingEnabled) {
throw new Error(`Provider ${item.provider} not available or warming disabled`);
}
// Perform warming operation
await this.performWarming(provider, item.key, item.metadata);
const duration = Date.now() - startTime;
// Update metrics
this.updateWarmingMetrics(item, duration, true);
this.warmingState.completed.add(warmingId);
this.emit('warmingCompleted', {
...item,
duration: duration,
success: true
});
} catch (error) {
const duration = Date.now() - (this.warmingState.startTime || Date.now());
// Update metrics
this.updateWarmingMetrics(item, duration, false);
this.warmingState.failed.add(warmingId);
logger.error('Cache warming failed:', {
provider: item.provider,
key: item.key,
error: error.message
});
this.emit('warmingFailed', {
...item,
duration: duration,
error: error,
success: false
});
// Retry with lower priority if not at max attempts
if (item.attempts < 3) {
item.attempts++;
const lowerPriority = item.priority === 'high' ? 'medium' : 'low';
setTimeout(() => {
this.addToWarmingQueue(item.provider, item.key, lowerPriority, item.metadata);
}, 30000 * item.attempts); // Exponential backoff
}
} finally {
this.warmingState.inProgress.delete(warmingId);
this.metrics.resources.concurrentWarming = this.warmingState.inProgress.size;
}
}
/**
* Perform actual warming operation
*/
async performWarming(provider, key, metadata = {}) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Warming timeout')),
this.config.resources.maxWarmingTime);
});
const warmingPromise = (async () => {
// Try different warming methods
if (provider.provider.warm) {
return await provider.provider.warm(key, metadata);
}
if (provider.provider.preload) {
return await provider.provider.preload(key, metadata);
}
if (provider.provider.get) {
return await provider.provider.get(key, { ...metadata, warming: true });
}
throw new Error('No warming method available');
})();
return await Promise.race([warmingPromise, timeoutPromise]);
}
/**
* Update warming metrics
*/
updateWarmingMetrics(item, duration, success) {
this.metrics.warming.total++;
this.metrics.warming.totalTime += duration;
this.metrics.warming.averageTime =
this.metrics.warming.totalTime / this.metrics.warming.total;
if (success) {
this.metrics.warming.successful++;
} else {
this.metrics.warming.failed++;
}
// Update priority metrics
if (!this.metrics.warming.byPriority.has(item.priority)) {
this.metrics.warming.byPriority.set(item.priority, {
total: 0,
successful: 0,
failed: 0,
averageTime: 0,
totalTime: 0
});
}
const priorityStats = this.metrics.warming.byPriority.get(item.priority);
priorityStats.total++;
priorityStats.totalTime += duration;
priorityStats.averageTime = priorityStats.totalTime / priorityStats.total;
if (success) {
priorityStats.successful++;
} else {
priorityStats.failed++;
}
// Update strategy metrics
const strategy = item.metadata.reason || 'unknown';
if (!this.metrics.warming.byStrategy.has(strategy)) {
this.metrics.warming.byStrategy.set(strategy, {
total: 0,
successful: 0,
failed: 0
});
}
const strategyStats = this.metrics.warming.byStrategy.get(strategy);
strategyStats.total++;
if (success) {
strategyStats.successful++;
} else {
strategyStats.failed++;
}
}
/**
* Run predictive warming
*/
async runPredictiveWarming() {
if (!this.config.predictive.enabled) return;
try {
const predictions = await this.predictor.generatePredictions();
for (const prediction of predictions) {
if (prediction.confidence >= this.config.predictive.confidenceThreshold) {
this.addToWarmingQueue(
prediction.provider,
prediction.key,
prediction.priority,
{
reason: 'predictive',
confidence: prediction.confidence,
prediction: prediction
}
);
}
}
this.metrics.patterns.predicted += predictions.length;
this.emit('predictiveWarming', {
predictions: predictions.length,
queued: predictions.filter(p =>
p.confidence >= this.config.predictive.confidenceThreshold
).length
});
} catch (error) {
logger.error('Predictive warming failed:', error);
}
}
/**
* Run time-based warming
*/
async runTimeBasedWarming() {
try {
const timeBasedItems = this.patternAnalyzer.getTimeBasedPatterns();
for (const item of timeBasedItems) {
if (this.shouldWarmBasedOnTime(item)) {
this.addToWarmingQueue(
item.provider,
item.key,
'medium',
{
reason: 'time_based',
pattern: item.pattern
}
);
}
}
this.emit('timeBasedWarming', {
items: timeBasedItems.length
});
} catch (error) {
logger.error('Time-based warming failed:', error);
}
}
/**
* Run pattern-based warming
*/
async runPatternBasedWarming() {
try {
const patterns = this.patternAnalyzer.getFrequentPatterns();
for (const pattern of patterns) {
if (this.shouldWarmBasedOnPattern(pattern)) {
this.addToWarmingQueue(
pattern.provider,
pattern.key,
this.calculatePatternPriority(pattern),
{
reason: 'pattern_based',
pattern: pattern
}
);
}
}
this.emit('patternBasedWarming', {
patterns: patterns.length
});
} catch (error) {
logger.error('Pattern-based warming failed:', error);
}
}
/**
* Run dependency warming
*/
async runDependencyWarming() {
try {
const dependencyChains = this.dependencyTracker.getDependencyChains();
for (const chain of dependencyChains) {
if (this.shouldWarmDependencyChain(chain)) {
for (const item of chain.items) {
this.addToWarmingQueue(
item.provider,
item.key,
'medium',
{
reason: 'dependency',
chain: chain.id,
dependencies: chain.dependencies
}
);
}
}
}
this.emit('dependencyWarming', {
chains: dependencyChains.length
});
} catch (error) {
logger.error('Dependency warming failed:', error);
}
}
/**
* Check if item should be warmed based on time patterns
*/
shouldWarmBasedOnTime(item) {
// Implement time-based logic (e.g., daily patterns, peak hours)
const now = new Date();
const hour = now.getHours();
// Example: warm popular items before peak hours (9 AM, 1 PM, 5 PM)
const peakHours = [9, 13, 17];
const currentMinute = now.getMinutes();
return peakHours.includes(hour) && currentMinute < 30 &&
item.frequency > this.config.patternAnalysis.minFrequency;
}
/**
* Check if item should be warmed based on patterns
*/
shouldWarmBasedOnPattern(pattern) {
return pattern.frequency > this.config.patternAnalysis.minFrequency &&
pattern.count >= this.config.patternAnalysis.minRequests &&
pattern.hitRate < 0.8; // Low hit rate indicates warming opportunity
}
/**
* Calculate priority for pattern-based warming
*/
calculatePatternPriority(pattern) {
const score = (pattern.frequency * 0.4) +
((1 - pattern.hitRate) * 0.4) +
(Math.log(pattern.count) * 0.2);
if (score > this.config.priority.highPriorityThreshold) return 'high';
if (score > this.config.priority.mediumPriorityThreshold) return 'medium';
return 'low';
}
/**
* Check if dependency chain should be warmed
*/
shouldWarmDependencyChain(chain) {
return chain.strength > 0.7 && chain.frequency > 0.1;
}
/**
* Force warming of specific cache items
*/
async forceWarming(provider, keys, priority = 'high') {
const warmingPromises = [];
for (const key of keys) {
warmingPromises.push(
this.warmCacheItem({
provider: provider,
key: key,
priority: priority,
timestamp: Date.now(),
attempts: 0,
metadata: { reason: 'forced' }
})
);
}
const results = await Promise.allSettled(warmingPromises);
this.emit('forcedWarming', {
provider: provider,
keys: keys,
results: results
});
return results;
}
/**
* Get warming statistics
*/
getWarmingStatistics() {
return {
timestamp: Date.now(),
// Overall metrics
warming: {
...this.metrics.warming,
successRate: this.metrics.warming.total > 0 ?
this.metrics.warming.successful / this.metrics.warming.total : 0,
byPriority: Object.fromEntries(this.metrics.warming.byPriority),
byStrategy: Object.fromEntries(this.metrics.warming.byStrategy)
},
// Pattern metrics
patterns: {
...this.metrics.patterns,
totalPatterns: this.accessPatterns.size,
activePatterns: Array.from(this.accessPatterns.values())
.filter(p => p.frequency > this.config.patternAnalysis.minFrequency).length
},
// Cache improvement metrics
cache: this.metrics.cache,
// Resource usage
resources: this.metrics.resources,
// Queue status
queues: {
high: this.warmingQueues.get('high')?.length || 0,
medium: this.warmingQueues.get('medium')?.length || 0,
low: this.warmingQueues.get('low')?.length || 0,
total: Array.from(this.warmingQueues.values())
.reduce((sum, queue) => sum + queue.length, 0)
},
// State
state: {
...this.warmingState,
inProgress: this.warmingState.inProgress.size,
completed: this.warmingState.completed.size,
failed: this.warmingState.failed.size
}
};
}
/**
* Get warming recommendations
*/
getWarmingRecommendations() {
const recommendations = [];
const stats = this.getWarmingStatistics();
// Success rate recommendations
if (stats.warming.successRate < 0.8) {
recommendations.push({
type: 'success_rate',
priority: 'high',
description: 'Cache warming success rate is low',
suggestions: [
'Review warming methods and provider configurations',
'Increase warming timeout limits',
'Check for provider availability issues',
'Optimize warming strategies'
]
});
}
// Queue size recommendations
if (stats.queues.total > 100) {
recommendations.push({
type: 'queue_size',
priority: 'medium',
description: 'Large warming queue detected',
suggestions: [
'Increase warming concurrency',
'Review warming priorities',
'Optimize warming performance',
'Consider reducing warming frequency'
]
});
}
// Pattern utilization recommendations
if (stats.patterns.activePatterns < stats.patterns.totalPatterns * 0.5) {
recommendations.push({
type: 'pattern_utilization',
priority: 'low',
description: 'Many patterns are not being utilized for warming',
suggestions: [
'Review pattern analysis thresholds',
'Optimize pattern detection algorithms',
'Consider pattern cleanup for unused patterns'
]
});
}
return recommendations;
}
/**
* Shutdown cache warmer
*/
async shutdown() {
try {
logger.info('Shutting down cache warmer');
// Clear monitoring intervals
for (const interval of this.monitoringIntervals) {
clearInterval(interval);
}
// Shutdown sub-systems
await this.predictor.shutdown();
await this.patternAnalyzer.shutdown();
await this.dependencyTracker.shutdown();
// Clear data structures
this.cacheProviders.clear();
this.accessPatterns.clear();
this.requestHistory = [];
this.warmingQueues.forEach(queue => queue.length = 0);
this.initialized = false;
this.emit('shutdown');
logger.info('Cache warmer shutdown completed');
} catch (error) {
logger.error('Error during cache warmer shutdown:', error);
throw error;
}
}
}
/**
* Cache Prediction Engine
*/
class CachePredictionEngine {
constructor(warmer) {
this.warmer = warmer;
this.models = new Map();
this.predictions = [];
}
async initialize() {
logger.debug('Cache prediction engine initialized');
}
async generatePredictions() {
const predictions = [];
// Simple pattern-based prediction
for (const [key, pattern] of this.warmer.accessPatterns) {
if (pattern.timePattern.length >= 3) {
const prediction = this.predictNextAccess(pattern);
if (prediction) {
predictions.push(prediction);
}
}
}
// Limit predictions
return predictions.slice(0, this.warmer.config.predictive.maxPredictions);
}
predictNextAccess(pattern) {
// Simple time-based prediction
const timePattern = pattern.timePattern.slice(-10);
if (timePattern.length < 3) return null;
// Calculate average interval
const intervals = [];
for (let i = 1; i < timePattern.length; i++) {
intervals.push(timePattern[i] - timePattern[i - 1]);
}
const avgInterval = intervals.reduce((sum, interval) => sum + interval, 0) / intervals.length;
const nextPredictedTime = pattern.lastAccess + avgInterval;
// Check if prediction is within look-ahead window
const now = Date.now();
const lookAheadTime = this.warmer.config.predictive.lookAheadTime;
if (nextPredictedTime > now && nextPredictedTime < now + lookAheadTime) {
return {
provider: pattern.provider,
key: pattern.key,
predictedTime: nextPredictedTime,
confidence: Math.min(0.9, pattern.frequency * 10), // Simple confidence
priority: pattern.frequency > 0.1 ? 'high' : 'medium'
};
}
return null;
}
async shutdown() {
this.models.clear();
this.predictions = [];
logger.debug('Cache prediction engine shutdown');
}
}
/**
* Access Pattern Analyzer
*/
class AccessPatternAnalyzer {
constructor(warmer) {
this.warmer = warmer;
this.timeBasedPatterns = new Map();
}
async initialize() {
logger.debug('Access pattern analyzer initialized');
}
async analyzePatterns() {
this.analyzeTimeBasedPatterns();
this.analyzeFrequencyPatterns();
this.warmer.metrics.patterns.detected++;
}
analyzeTimeBasedPatterns() {
// Analyze patterns based on time of day, day of week, etc.
for (const [key, pattern] of this.warmer.accessPatterns) {
const timeAnalysis = this.analyzeTimePattern(pattern.timePattern);
if (timeAnalysis.strength > 0.6) {
this.timeBasedPatterns.set(key, {
...pattern,
timeAnalysis: timeAnalysis
});
}
}
}
analyzeTimePattern(timePattern) {
if (timePattern.length < 5) return { strength: 0 };
// Simple analysis: check for regular intervals
const intervals = [];
for (let i = 1; i < timePattern.length; i++) {
intervals.push(timePattern[i] - timePattern[i - 1]);
}
// Calculate coefficient of variation
const mean = intervals.reduce((sum, val) => sum + val, 0) / intervals.length;
const variance = intervals.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / intervals.length;
const stdDev = Math.sqrt(variance);
const cv = mean > 0 ? stdDev / mean : 1;
// Lower CV indicates more regular pattern
const strength = Math.max(0, 1 - cv);
return {
strength: strength,
averageInterval: mean,
regularity: 1 - cv,
intervalCount: intervals.length
};
}
analyzeFrequencyPatterns() {
// Already handled in updateAccessPatterns
}
getTimeBasedPatterns() {
return Array.from(this.timeBasedPatterns.values());
}
getFrequentPatterns() {
return Array.from(this.warmer.accessPatterns.values())
.filter(pattern =>
pattern.frequency > this.warmer.config.patternAnalysis.minFrequency &&
pattern.count >= this.warmer.config.patternAnalysis.minRequests
);
}
async shutdown() {
this.timeBasedPatterns.clear();
logger.debug('Access pattern analyzer shutdown');
}
}
/**
* Dependency Tracker
*/
class DependencyTracker {
constructor(warmer) {
this.warmer = warmer;
this.dependencies = new Map();
this.chains = new Map();
}
async initialize() {
logger.debug('Dependency tracker initialized');
}
trackAccess(access) {
// Track access dependencies based on temporal proximity
const recentAccesses = this.warmer.requestHistory.slice(-10);
for (const recentAccess of recentAccesses) {
if (recentAccess.timestamp < access.timestamp - 60000) continue; // 1 minute window
if (recentAccess.key === access.key) continue; // Same key
const dependencyKey = `${recentAccess.provider}:${recentAccess.key}`;
const dependentKey = `${access.provider}:${access.key}`;
this.updateDependency(dependencyKey, dependentKey);
}
}
updateDependency(dependencyKey, dependentKey) {
if (!this.dependencies.has(dependencyKey)) {
this.dependencies.set(dependencyKey, new Map());
}
const dependents = this.dependencies.get(dependencyKey);
const count = dependents.get(dependentKey) || 0;
dependents.set(dependentKey, count + 1);
}
getDependencyChains() {
const chains = [];
for (const [dependency, dependents] of this.dependencies) {
const totalCount = Array.from(dependents.values()).reduce((sum, count) => sum + count, 0);
if (totalCount >= 3) { // Minimum strength threshold
chains.push({
id: dependency,
root: dependency,
items: Array.from(dependents.keys()).map(key => {
const [provider, cacheKey] = key.split(':');
return { provider, key: cacheKey };
}),
strength: Math.min(1, totalCount / 10),
frequency: totalCount / this.warmer.requestHistory.length,
dependencies: Object.fromEntries(dependents)
});
}
}
return chains;
}
async shutdown() {
this.dependencies.clear();
this.chains.clear();
logger.debug('Dependency tracker shutdown');
}
}
export default CacheWarmer;