UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

292 lines 10.6 kB
/** * TTL (Time To Live) Strategy for Intelligent Query Caching * * Provides dynamic TTL configuration based on query characteristics, * data volatility, and usage patterns. */ import { getLogger } from '../../../logging/Logger.js'; export class TTLStrategy { logger = getLogger(); // Default configuration defaultConfig = { base: 300000, // 5 minutes min: 30000, // 30 seconds max: 3600000, // 1 hour multipliers: { entity: { // Static entities - longer cache project: 2.0, environment: 2.0, attribute: 2.0, // Semi-static entities flag: 1.5, audience: 1.5, event: 1.5, // Dynamic entities - shorter cache experiment: 0.8, campaign: 0.8, variation: 0.8, rule: 0.8, // Highly dynamic results: 0.5, metrics: 0.5, }, operation: { // Read operations list: 1.0, detail: 1.0, select: 1.0, // Analysis operations - cache longer count: 1.5, aggregate: 2.0, analyze: 2.0, compare: 1.8, }, timeRange: { // Historical data - cache longer absolute_past: 3.0, relative_past: 2.0, // Current data - shorter cache today: 0.5, this_week: 0.7, this_month: 0.8, // Real-time realtime: 0.1, }, complexity: { simple: 1.0, moderate: 1.5, complex: 2.0, } } }; config; constructor(customConfig) { this.config = this.mergeConfig(this.defaultConfig, customConfig); this.logger.info({ config: this.config }, 'TTLStrategy initialized'); } /** * Calculate TTL for a query based on various factors */ calculateTTL(query, context, factors) { this.logger.debug({ query, context, factors }, 'Calculating TTL'); let ttl = this.config.base; // Apply entity multiplier const entityMultiplier = this.config.multipliers.entity[query.entity] || 1.0; ttl *= entityMultiplier; // Apply operation multiplier const operationMultiplier = this.config.multipliers.operation[query.operation] || 1.0; ttl *= operationMultiplier; // Apply time range multiplier const timeRangeMultiplier = this.getTimeRangeMultiplier(query.timeRange); ttl *= timeRangeMultiplier; // Apply complexity multiplier if provided if (factors?.queryComplexity) { const complexityMultiplier = this.config.multipliers.complexity[factors.queryComplexity] || 1.0; ttl *= complexityMultiplier; } // Adjust for data volatility if (factors?.dataVolatility) { ttl = this.adjustForVolatility(ttl, factors.dataVolatility); } // Special adjustments ttl = this.applySpecialRules(ttl, query, context, factors); // Ensure within bounds ttl = Math.max(this.config.min, Math.min(this.config.max, Math.round(ttl))); this.logger.debug({ calculatedTTL: ttl, ttlMinutes: ttl / 60000, multipliers: { entity: entityMultiplier, operation: operationMultiplier, timeRange: timeRangeMultiplier, } }, 'TTL calculated'); return ttl; } /** * Get time range multiplier */ getTimeRangeMultiplier(timeRange) { if (!timeRange) return 1.0; if (timeRange.type === 'relative') { switch (timeRange.duration) { case 'today': return this.config.multipliers.timeRange.today || 0.5; case 'yesterday': return this.config.multipliers.timeRange.relative_past || 2.0; case 'this_week': return this.config.multipliers.timeRange.this_week || 0.7; case 'this_month': return this.config.multipliers.timeRange.this_month || 0.8; case 'this_year': return 1.0; default: // For "last_X_days" patterns if (timeRange.duration?.startsWith('last_')) { return this.config.multipliers.timeRange.relative_past || 2.0; } return 1.0; } } else { // Absolute time range const now = Date.now(); const end = timeRange.end ? new Date(timeRange.end).getTime() : now; // If end date is in the past, cache longer if (end < now - 86400000) { // More than 1 day ago return this.config.multipliers.timeRange.absolute_past || 3.0; } return 1.0; } } /** * Adjust TTL based on data volatility */ adjustForVolatility(ttl, volatility) { switch (volatility) { case 'static': return ttl * 3.0; case 'low': return ttl * 1.5; case 'medium': return ttl * 1.0; case 'high': return ttl * 0.5; default: return ttl; } } /** * Apply special rules based on query characteristics */ applySpecialRules(ttl, query, context, factors) { // Real-time queries if (query.filters.realtime || query.filters.live) { return this.config.min; // Minimum TTL for real-time data } // Queries with specific IDs tend to be more stable if (query.filters.id || query.filters.key) { ttl *= 1.5; } // Aggregations with grouping are expensive, cache longer if (query.groupBy && query.groupBy.length > 0) { ttl *= 1.5; } // Multiple joins increase complexity, cache longer if (query.joins.length > 2) { ttl *= 1.3; } // Production environment data changes less frequently if (context?.environment === 'production') { ttl *= 1.5; } // Staging/development data changes more frequently if (context?.environment === 'development' || context?.environment === 'staging') { ttl *= 0.7; } // Large result sets (implied by high entity count) if (factors?.entityCount && factors.entityCount > 1000) { ttl *= 1.5; // Cache expensive queries longer } return ttl; } /** * Get recommended TTL for specific scenarios */ getRecommendedTTL(scenario) { const recommendations = { // Real-time monitoring 'realtime-dashboard': 30000, // 30 seconds 'live-metrics': 30000, // 30 seconds // Regular operations 'flag-list': 300000, // 5 minutes 'experiment-details': 180000, // 3 minutes 'audience-list': 600000, // 10 minutes // Analytics 'daily-report': 3600000, // 1 hour 'historical-analysis': 3600000, // 1 hour 'trend-analysis': 1800000, // 30 minutes // Configuration 'project-settings': 1800000, // 30 minutes 'environment-config': 1800000, // 30 minutes }; return recommendations[scenario] || this.config.base; } /** * Determine if a cached result should be refreshed based on age */ shouldRefresh(cachedAt, ttl, softRefreshRatio = 0.8) { const age = Date.now() - cachedAt; const softRefreshThreshold = ttl * softRefreshRatio; return age > softRefreshThreshold; } /** * Calculate dynamic TTL based on access patterns */ calculateDynamicTTL(baseTTL, accessCount, lastAccessInterval) { // High access count = popular query = cache longer const popularityMultiplier = Math.min(2.0, 1 + (accessCount / 100)); // Recent frequent access = cache longer const frequencyMultiplier = lastAccessInterval < 60000 ? 1.5 : 1.0; let dynamicTTL = baseTTL * popularityMultiplier * frequencyMultiplier; // Ensure within bounds return Math.max(this.config.min, Math.min(this.config.max, Math.round(dynamicTTL))); } /** * Get cache warmup priority based on query characteristics */ getCacheWarmupPriority(query) { let priority = 0; // Common queries get higher priority if (['list', 'count'].includes(query.operation)) { priority += 3; } // Production queries get higher priority if (query.filters.environment === 'production') { priority += 2; } // Frequently accessed entities if (['flag', 'experiment', 'audience'].includes(query.entity)) { priority += 2; } // Complex queries get lower priority (expensive to warm up) if (query.joins.length > 1 || query.aggregations.length > 0) { priority -= 1; } return Math.max(0, priority); } /** * Merge configurations */ mergeConfig(base, custom) { if (!custom) return base; return { base: custom.base ?? base.base, min: custom.min ?? base.min, max: custom.max ?? base.max, multipliers: { entity: { ...base.multipliers.entity, ...custom.multipliers?.entity }, operation: { ...base.multipliers.operation, ...custom.multipliers?.operation }, timeRange: { ...base.multipliers.timeRange, ...custom.multipliers?.timeRange }, complexity: { ...base.multipliers.complexity, ...custom.multipliers?.complexity }, }, }; } /** * Get configuration (for monitoring/debugging) */ getConfig() { return { ...this.config }; } /** * Update configuration at runtime */ updateConfig(updates) { this.config = this.mergeConfig(this.config, updates); this.logger.info({ config: this.config }, 'TTL configuration updated'); } } //# sourceMappingURL=TTLStrategy.js.map