@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
292 lines • 10.6 kB
JavaScript
/**
* 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