Skip to content

CacheService Documentation

Comprehensive CacheService Implementation Guide

The CacheService provides Redis-based caching capabilities for search results, analytics data, and performance optimization. It implements intelligent caching strategies, data structures optimization, and comprehensive analytics storage for enhanced system performance.

Service Overview

Responsibilities

The CacheService handles:

  • Search Result Caching - Cache frequently accessed search results for improved response times
  • Analytics Storage - Store and retrieve search analytics and user behavior data
  • Performance Optimization - Reduce database load through intelligent caching strategies
  • Session Management - Manage user sessions and temporary data storage
  • Real-time Metrics - Track and store real-time performance metrics

Architecture

typescript
export class CacheService {
  constructor(
    private redisClient: Redis,
    private logger: Logger,
    private config: CacheConfig
  ) {}

  // Core Caching Methods
  async get<T>(key: string): Promise<T | null>;
  async set<T>(key: string, value: T, ttl?: number): Promise<void>;
  async del(key: string): Promise<boolean>;
  async exists(key: string): Promise<boolean>;
  async expire(key: string, ttl: number): Promise<boolean>;

  // Search Caching
  async cacheSearchResults(
    query: SearchQuery,
    results: SearchResult[],
    ttl?: number
  ): Promise<void>;
  async getCachedSearchResults(
    query: SearchQuery
  ): Promise<SearchResult[] | null>;
  async invalidateSearchCache(pattern?: string): Promise<number>;

  // Analytics Methods
  async storeAnalytics(event: AnalyticsEvent): Promise<void>;
  async getAnalytics(query: AnalyticsQuery): Promise<AnalyticsData>;
  async aggregateMetrics(timeframe: string): Promise<AggregatedMetrics>;

  // Session Management
  async createSession(userId: string, data: SessionData): Promise<string>;
  async getSession(sessionId: string): Promise<SessionData | null>;
  async updateSession(
    sessionId: string,
    data: Partial<SessionData>
  ): Promise<void>;
  async destroySession(sessionId: string): Promise<boolean>;

  // Health and Monitoring
  async healthCheck(): Promise<CacheHealth>;
  async getStats(): Promise<CacheStats>;
}

Core Functionality

Basic Caching Operations

Get, Set, and Delete Operations

The service provides fundamental caching operations with advanced features:

typescript
interface CacheConfig {
  host: string
  port: number
  password?: string
  database: number
  connectionTimeout: number
  commandTimeout: number
  retryDelayOnFailover: number
  maxRetriesPerRequest: number
  keyPrefix: string
  defaultTTL: number
}

interface CacheMetrics {
  hits: number
  misses: number
  sets: number
  deletes: number
  errors: number
}

async get<T>(key: string): Promise<T | null> {
  const startTime = Date.now()

  try {
    const fullKey = this.buildKey(key)
    const value = await this.redisClient.get(fullKey)

    const operationTime = Date.now() - startTime

    if (value === null) {
      this.updateMetrics('miss', operationTime)
      this.logger.debug('Cache miss', { key: fullKey, operationTime })
      return null
    }

    const parsed = JSON.parse(value)
    this.updateMetrics('hit', operationTime)

    this.logger.debug('Cache hit', {
      key: fullKey,
      operationTime,
      dataSize: value.length
    })

    return parsed

  } catch (error) {
    const operationTime = Date.now() - startTime
    this.updateMetrics('error', operationTime)
    this.logger.error('Cache get failed', { error, key, operationTime })

    // Return null on cache errors to allow fallback to source
    return null
  }
}

async set<T>(key: string, value: T, ttl?: number): Promise<void> {
  const startTime = Date.now()

  try {
    const fullKey = this.buildKey(key)
    const serialized = JSON.stringify(value)
    const cacheTTL = ttl || this.config.defaultTTL

    // Use SETEX for atomic set with expiration
    await this.redisClient.setex(fullKey, cacheTTL, serialized)

    const operationTime = Date.now() - startTime
    this.updateMetrics('set', operationTime)

    this.logger.debug('Cache set', {
      key: fullKey,
      ttl: cacheTTL,
      operationTime,
      dataSize: serialized.length
    })

  } catch (error) {
    const operationTime = Date.now() - startTime
    this.updateMetrics('error', operationTime)
    this.logger.error('Cache set failed', { error, key, operationTime })

    // Don't throw on cache errors to prevent disrupting main operations
    return
  }
}

async del(key: string): Promise<boolean> {
  const startTime = Date.now()

  try {
    const fullKey = this.buildKey(key)
    const result = await this.redisClient.del(fullKey)

    const operationTime = Date.now() - startTime
    this.updateMetrics('delete', operationTime)

    this.logger.debug('Cache delete', {
      key: fullKey,
      found: result > 0,
      operationTime
    })

    return result > 0

  } catch (error) {
    const operationTime = Date.now() - startTime
    this.updateMetrics('error', operationTime)
    this.logger.error('Cache delete failed', { error, key, operationTime })
    return false
  }
}

private buildKey(key: string): string {
  return `${this.config.keyPrefix}${key}`
}

private updateMetrics(operation: string, time: number): void {
  // Update internal metrics for monitoring
  this.metrics[operation] = (this.metrics[operation] || 0) + 1
  this.responseTimeMetrics.push({ operation, time, timestamp: Date.now() })

  // Keep only recent metrics (last 1000 operations)
  if (this.responseTimeMetrics.length > 1000) {
    this.responseTimeMetrics.shift()
  }
}

Search Result Caching

Advanced Search Cache Management

The service implements sophisticated search result caching with invalidation strategies:

typescript
interface SearchCacheKey {
  query: string
  databases: string[]
  mode: SearchMode
  filters: SearchFilters
  limit: number
  offset: number
}

interface CachedSearchResult {
  results: SearchResult[]
  metadata: SearchMetadata
  cachedAt: Date
  expiresAt: Date
  hitCount: number
}

async cacheSearchResults(
  query: SearchQuery,
  results: SearchResult[],
  ttl?: number
): Promise<void> {
  try {
    const cacheKey = this.generateSearchCacheKey(query)
    const cacheData: CachedSearchResult = {
      results,
      metadata: {
        totalResults: results.length,
        executionTime: query.executionTime,
        databases: query.databases,
        mode: query.mode
      },
      cachedAt: new Date(),
      expiresAt: new Date(Date.now() + (ttl || this.getSearchCacheTTL(query))),
      hitCount: 0
    }

    await this.set(cacheKey, cacheData, ttl)

    // Store cache key for pattern-based invalidation
    await this.addToCacheIndex('search', cacheKey, query)

    this.logger.info('Search results cached', {
      cacheKey,
      resultCount: results.length,
      ttl: ttl || this.getSearchCacheTTL(query)
    })

  } catch (error) {
    this.logger.error('Failed to cache search results', { error, query })
  }
}

async getCachedSearchResults(query: SearchQuery): Promise<SearchResult[] | null> {
  try {
    const cacheKey = this.generateSearchCacheKey(query)
    const cached = await this.get<CachedSearchResult>(cacheKey)

    if (!cached) {
      return null
    }

    // Update hit count
    cached.hitCount += 1
    await this.set(cacheKey, cached, Math.floor((cached.expiresAt.getTime() - Date.now()) / 1000))

    this.logger.debug('Search cache hit', {
      cacheKey,
      hitCount: cached.hitCount,
      resultCount: cached.results.length
    })

    return cached.results

  } catch (error) {
    this.logger.error('Failed to get cached search results', { error, query })
    return null
  }
}

private generateSearchCacheKey(query: SearchQuery): string {
  // Create deterministic cache key from query parameters
  const keyObject: SearchCacheKey = {
    query: query.query.toLowerCase().trim(),
    databases: query.databases.sort(),
    mode: query.mode,
    filters: this.normalizeFilters(query.filters),
    limit: query.limit,
    offset: query.offset
  }

  const hash = require('crypto')
    .createHash('md5')
    .update(JSON.stringify(keyObject))
    .digest('hex')

  return `search:${hash}`
}

private getSearchCacheTTL(query: SearchQuery): number {
  // Dynamic TTL based on query characteristics
  const baseTTL = 300 // 5 minutes

  // Longer TTL for popular queries
  if (this.isPopularQuery(query.query)) {
    return baseTTL * 4 // 20 minutes
  }

  // Shorter TTL for real-time data queries
  if (this.hasRecentFilters(query.filters)) {
    return baseTTL / 5 // 1 minute
  }

  // Longer TTL for complex queries (expensive to recompute)
  if (query.mode === 'semantic' || query.databases.length > 3) {
    return baseTTL * 2 // 10 minutes
  }

  return baseTTL
}

async invalidateSearchCache(pattern?: string): Promise<number> {
  try {
    let keysToDelete: string[]

    if (pattern) {
      // Pattern-based invalidation
      const fullPattern = this.buildKey(`search:*${pattern}*`)
      keysToDelete = await this.redisClient.keys(fullPattern)
    } else {
      // Invalidate all search cache
      const allSearchKeys = this.buildKey('search:*')
      keysToDelete = await this.redisClient.keys(allSearchKeys)
    }

    if (keysToDelete.length === 0) {
      return 0
    }

    // Delete in batches to avoid blocking
    const batchSize = 100
    let deletedCount = 0

    for (let i = 0; i < keysToDelete.length; i += batchSize) {
      const batch = keysToDelete.slice(i, i + batchSize)
      const result = await this.redisClient.del(...batch)
      deletedCount += result
    }

    this.logger.info('Search cache invalidated', {
      pattern,
      deletedKeys: deletedCount
    })

    return deletedCount

  } catch (error) {
    this.logger.error('Failed to invalidate search cache', { error, pattern })
    return 0
  }
}

Analytics Storage

Comprehensive Analytics Data Management

The service provides advanced analytics storage and retrieval capabilities:

typescript
interface AnalyticsEvent {
  type: 'search' | 'user_action' | 'performance' | 'error'
  userId?: string
  sessionId?: string
  timestamp: Date
  data: Record<string, any>
  metadata?: {
    userAgent?: string
    ip?: string
    source?: string
  }
}

interface AnalyticsQuery {
  type: string
  dateRange: {
    from: Date
    to: Date
  }
  userId?: string
  filters?: Record<string, any>
  aggregation?: 'sum' | 'avg' | 'count' | 'max' | 'min'
  groupBy?: string[]
}

interface TimeSeriesData {
  timestamp: Date
  value: number
  metadata?: Record<string, any>
}

async storeAnalytics(event: AnalyticsEvent): Promise<void> {
  try {
    const timestamp = event.timestamp.getTime()
    const eventKey = `analytics:${event.type}:${timestamp}:${Math.random().toString(36).substr(2)}`

    // Store individual event
    await this.set(eventKey, event, 86400) // 24 hours TTL

    // Update time-series data structures
    await this.updateTimeSeries(event)

    // Update counters and aggregates
    await this.updateAggregates(event)

    this.logger.debug('Analytics event stored', {
      type: event.type,
      userId: event.userId,
      timestamp: event.timestamp
    })

  } catch (error) {
    this.logger.error('Failed to store analytics event', { error, event })
  }
}

private async updateTimeSeries(event: AnalyticsEvent): Promise<void> {
  const timestamp = Math.floor(event.timestamp.getTime() / 1000)

  // Store hourly time series
  const hourKey = `timeseries:${event.type}:hour:${Math.floor(timestamp / 3600) * 3600}`
  await this.redisClient.zincrby(hourKey, 1, timestamp.toString())
  await this.redisClient.expire(hourKey, 86400 * 7) // 7 days

  // Store daily time series
  const dayKey = `timeseries:${event.type}:day:${Math.floor(timestamp / 86400) * 86400}`
  await this.redisClient.zincrby(dayKey, 1, Math.floor(timestamp / 3600).toString())
  await this.redisClient.expire(dayKey, 86400 * 30) // 30 days

  // Store weekly time series
  const weekKey = `timeseries:${event.type}:week:${Math.floor(timestamp / (86400 * 7)) * (86400 * 7)}`
  await this.redisClient.zincrby(weekKey, 1, Math.floor(timestamp / 86400).toString())
  await this.redisClient.expire(weekKey, 86400 * 365) // 1 year
}

private async updateAggregates(event: AnalyticsEvent): Promise<void> {
  const hourBucket = Math.floor(event.timestamp.getTime() / (1000 * 3600))
  const dayBucket = Math.floor(event.timestamp.getTime() / (1000 * 86400))

  // Update hourly counters
  await this.redisClient.hincrby(`counter:${event.type}:hour`, hourBucket.toString(), 1)
  await this.redisClient.expire(`counter:${event.type}:hour`, 86400 * 7)

  // Update daily counters
  await this.redisClient.hincrby(`counter:${event.type}:day`, dayBucket.toString(), 1)
  await this.redisClient.expire(`counter:${event.type}:day`, 86400 * 30)

  // Update specific metrics based on event type
  if (event.type === 'search') {
    await this.updateSearchAggregates(event)
  } else if (event.type === 'performance') {
    await this.updatePerformanceAggregates(event)
  }
}

private async updateSearchAggregates(event: AnalyticsEvent): Promise<void> {
  const query = event.data.query?.toLowerCase()
  if (!query) return

  // Track popular queries
  await this.redisClient.zincrby('popular:queries', 1, query)

  // Track search modes usage
  if (event.data.mode) {
    await this.redisClient.hincrby('stats:search_modes', event.data.mode, 1)
  }

  // Track database usage
  if (event.data.databases) {
    for (const dbId of event.data.databases) {
      await this.redisClient.zincrby('popular:databases', 1, dbId)
    }
  }
}

async getAnalytics(query: AnalyticsQuery): Promise<AnalyticsData> {
  try {
    const cacheKey = this.generateAnalyticsCacheKey(query)
    const cached = await this.get<AnalyticsData>(cacheKey)

    if (cached) {
      this.logger.debug('Analytics cache hit', { cacheKey })
      return cached
    }

    const data = await this.computeAnalytics(query)

    // Cache analytics data for 5 minutes
    await this.set(cacheKey, data, 300)

    return data

  } catch (error) {
    this.logger.error('Failed to get analytics', { error, query })
    throw new AppError('ANALYTICS_RETRIEVAL_FAILED', error.message)
  }
}

private async computeAnalytics(query: AnalyticsQuery): Promise<AnalyticsData> {
  const { type, dateRange, userId, filters, aggregation, groupBy } = query

  // Get time series data
  const timeSeriesData = await this.getTimeSeriesData(type, dateRange, aggregation || 'count')

  // Get aggregated metrics
  const aggregates = await this.getAggregatedMetrics(type, dateRange, filters)

  // Apply grouping if requested
  let groupedData: Record<string, any> = {}
  if (groupBy && groupBy.length > 0) {
    groupedData = await this.getGroupedAnalytics(type, dateRange, groupBy, aggregation)
  }

  return {
    type,
    dateRange,
    timeSeries: timeSeriesData,
    aggregates,
    grouped: groupedData,
    metadata: {
      computedAt: new Date(),
      dataPoints: timeSeriesData.length,
      cached: false
    }
  }
}

Session Management

Redis-based Session Storage

The service provides comprehensive session management capabilities:

typescript
interface SessionData {
  userId: string
  apiKeyId?: string
  permissions: string[]
  metadata: {
    userAgent?: string
    ip?: string
    createdAt: Date
    lastActivity: Date
    expiresAt: Date
  }
  preferences?: {
    defaultSearchMode?: SearchMode
    preferredDatabases?: string[]
    cachePreference?: boolean
  }
  temporaryData?: Record<string, any>
}

interface SessionConfig {
  defaultTTL: number
  extendOnActivity: boolean
  maxSessions: number
  secureMode: boolean
}

async createSession(userId: string, data: Partial<SessionData>): Promise<string> {
  try {
    const sessionId = this.generateSessionId()
    const now = new Date()

    const sessionData: SessionData = {
      userId,
      apiKeyId: data.apiKeyId,
      permissions: data.permissions || [],
      metadata: {
        userAgent: data.metadata?.userAgent,
        ip: data.metadata?.ip,
        createdAt: now,
        lastActivity: now,
        expiresAt: new Date(now.getTime() + this.config.session.defaultTTL)
      },
      preferences: data.preferences || {},
      temporaryData: {}
    }

    // Store session
    const sessionKey = `session:${sessionId}`
    await this.set(sessionKey, sessionData, this.config.session.defaultTTL / 1000)

    // Track user sessions
    await this.redisClient.sadd(`user_sessions:${userId}`, sessionId)
    await this.redisClient.expire(`user_sessions:${userId}`, this.config.session.defaultTTL / 1000)

    this.logger.info('Session created', {
      sessionId,
      userId,
      expiresAt: sessionData.metadata.expiresAt
    })

    return sessionId

  } catch (error) {
    this.logger.error('Failed to create session', { error, userId })
    throw new AppError('SESSION_CREATION_FAILED', error.message)
  }
}

async getSession(sessionId: string): Promise<SessionData | null> {
  try {
    const sessionKey = `session:${sessionId}`
    const session = await this.get<SessionData>(sessionKey)

    if (!session) {
      this.logger.debug('Session not found', { sessionId })
      return null
    }

    // Check if session is expired
    if (new Date() > new Date(session.metadata.expiresAt)) {
      await this.destroySession(sessionId)
      this.logger.debug('Session expired', { sessionId })
      return null
    }

    // Update last activity if configured
    if (this.config.session.extendOnActivity) {
      session.metadata.lastActivity = new Date()
      session.metadata.expiresAt = new Date(Date.now() + this.config.session.defaultTTL)
      await this.set(sessionKey, session, this.config.session.defaultTTL / 1000)
    }

    return session

  } catch (error) {
    this.logger.error('Failed to get session', { error, sessionId })
    return null
  }
}

async updateSession(sessionId: string, updates: Partial<SessionData>): Promise<void> {
  try {
    const session = await this.getSession(sessionId)
    if (!session) {
      throw new AppError('SESSION_NOT_FOUND', 'Session does not exist')
    }

    // Merge updates
    const updatedSession = {
      ...session,
      ...updates,
      metadata: {
        ...session.metadata,
        ...updates.metadata,
        lastActivity: new Date()
      }
    }

    const sessionKey = `session:${sessionId}`
    const remainingTTL = Math.max(0, Math.floor(
      (new Date(updatedSession.metadata.expiresAt).getTime() - Date.now()) / 1000
    ))

    await this.set(sessionKey, updatedSession, remainingTTL)

    this.logger.debug('Session updated', { sessionId })

  } catch (error) {
    this.logger.error('Failed to update session', { error, sessionId })
    throw new AppError('SESSION_UPDATE_FAILED', error.message)
  }
}

async destroySession(sessionId: string): Promise<boolean> {
  try {
    const session = await this.get<SessionData>(`session:${sessionId}`)

    // Delete session data
    const deleted = await this.del(`session:${sessionId}`)

    // Remove from user session tracking
    if (session?.userId) {
      await this.redisClient.srem(`user_sessions:${session.userId}`, sessionId)
    }

    this.logger.info('Session destroyed', { sessionId, found: deleted })

    return deleted

  } catch (error) {
    this.logger.error('Failed to destroy session', { error, sessionId })
    return false
  }
}

private generateSessionId(): string {
  const timestamp = Date.now().toString(36)
  const random = Math.random().toString(36).substr(2)
  return `sess_${timestamp}_${random}`
}

Health Monitoring and Statistics

Cache Health and Performance Monitoring

The service provides comprehensive health monitoring and performance statistics:

typescript
interface CacheHealth {
  status: 'healthy' | 'degraded' | 'unhealthy'
  redis: {
    connected: boolean
    responseTime: number
    memoryUsage: number
    hitRate: number
    keyCount: number
  }
  performance: {
    averageResponseTime: number
    operationsPerSecond: number
    errorRate: number
  }
  lastChecked: Date
}

interface CacheStats {
  operations: {
    gets: number
    sets: number
    deletes: number
    hits: number
    misses: number
    errors: number
  }
  performance: {
    averageGetTime: number
    averageSetTime: number
    hitRate: number
    errorRate: number
  }
  memory: {
    used: number
    peak: number
    fragmentation: number
  }
  connections: {
    total: number
    active: number
    idle: number
  }
}

async healthCheck(): Promise<CacheHealth> {
  const startTime = Date.now()

  try {
    // Test basic connectivity
    await this.redisClient.ping()
    const responseTime = Date.now() - startTime

    // Get Redis info
    const info = await this.redisClient.info()
    const memoryInfo = this.parseRedisInfo(info, 'memory')
    const statsInfo = this.parseRedisInfo(info, 'stats')

    // Calculate hit rate
    const hits = parseInt(statsInfo.keyspace_hits) || 0
    const misses = parseInt(statsInfo.keyspace_misses) || 0
    const hitRate = hits + misses > 0 ? hits / (hits + misses) : 0

    // Get key count
    const keyCount = await this.redisClient.dbsize()

    // Calculate performance metrics
    const performance = this.calculatePerformanceMetrics()

    const health: CacheHealth = {
      status: this.determineHealthStatus(responseTime, performance, hitRate),
      redis: {
        connected: true,
        responseTime,
        memoryUsage: parseInt(memoryInfo.used_memory) || 0,
        hitRate,
        keyCount
      },
      performance: {
        averageResponseTime: performance.averageResponseTime,
        operationsPerSecond: performance.operationsPerSecond,
        errorRate: performance.errorRate
      },
      lastChecked: new Date()
    }

    this.logger.debug('Cache health check completed', {
      status: health.status,
      responseTime,
      hitRate,
      keyCount
    })

    return health

  } catch (error) {
    this.logger.error('Cache health check failed', { error })

    return {
      status: 'unhealthy',
      redis: {
        connected: false,
        responseTime: Date.now() - startTime,
        memoryUsage: 0,
        hitRate: 0,
        keyCount: 0
      },
      performance: {
        averageResponseTime: 0,
        operationsPerSecond: 0,
        errorRate: 1
      },
      lastChecked: new Date()
    }
  }
}

async getStats(): Promise<CacheStats> {
  try {
    const info = await this.redisClient.info()
    const statsInfo = this.parseRedisInfo(info, 'stats')
    const memoryInfo = this.parseRedisInfo(info, 'memory')
    const clientsInfo = this.parseRedisInfo(info, 'clients')

    // Calculate internal metrics
    const performance = this.calculatePerformanceMetrics()

    const stats: CacheStats = {
      operations: {
        gets: this.metrics.get || 0,
        sets: this.metrics.set || 0,
        deletes: this.metrics.delete || 0,
        hits: this.metrics.hit || 0,
        misses: this.metrics.miss || 0,
        errors: this.metrics.error || 0
      },
      performance: {
        averageGetTime: performance.averageGetTime,
        averageSetTime: performance.averageSetTime,
        hitRate: performance.hitRate,
        errorRate: performance.errorRate
      },
      memory: {
        used: parseInt(memoryInfo.used_memory) || 0,
        peak: parseInt(memoryInfo.used_memory_peak) || 0,
        fragmentation: parseFloat(memoryInfo.mem_fragmentation_ratio) || 0
      },
      connections: {
        total: parseInt(clientsInfo.connected_clients) || 0,
        active: parseInt(clientsInfo.client_recent_max_input_buffer) || 0,
        idle: parseInt(clientsInfo.blocked_clients) || 0
      }
    }

    return stats

  } catch (error) {
    this.logger.error('Failed to get cache stats', { error })
    throw new AppError('CACHE_STATS_FAILED', error.message)
  }
}

private determineHealthStatus(
  responseTime: number,
  performance: any,
  hitRate: number
): 'healthy' | 'degraded' | 'unhealthy' {
  // Unhealthy conditions
  if (responseTime > 1000) return 'unhealthy'
  if (performance.errorRate > 0.1) return 'unhealthy'
  if (hitRate < 0.3) return 'unhealthy'

  // Degraded conditions
  if (responseTime > 100) return 'degraded'
  if (performance.errorRate > 0.01) return 'degraded'
  if (hitRate < 0.6) return 'degraded'

  return 'healthy'
}

private parseRedisInfo(info: string, section: string): Record<string, string> {
  const lines = info.split('\r\n')
  const sectionStart = lines.findIndex(line => line === `# ${section.charAt(0).toUpperCase() + section.slice(1)}`)

  if (sectionStart === -1) return {}

  const result: Record<string, string> = {}

  for (let i = sectionStart + 1; i < lines.length; i++) {
    const line = lines[i]
    if (line.startsWith('#') || line === '') break

    const [key, value] = line.split(':')
    if (key && value) {
      result[key] = value
    }
  }

  return result
}

Advanced Features

Cache Warming and Preloading

typescript
interface CacheWarmingStrategy {
  popularQueries: boolean
  recentSearches: boolean
  userPreferences: boolean
  staticData: boolean
}

async warmCache(strategy: CacheWarmingStrategy): Promise<void> {
  try {
    this.logger.info('Starting cache warming', { strategy })

    const tasks: Promise<void>[] = []

    if (strategy.popularQueries) {
      tasks.push(this.warmPopularQueries())
    }

    if (strategy.recentSearches) {
      tasks.push(this.warmRecentSearches())
    }

    if (strategy.userPreferences) {
      tasks.push(this.warmUserPreferences())
    }

    if (strategy.staticData) {
      tasks.push(this.warmStaticData())
    }

    await Promise.allSettled(tasks)

    this.logger.info('Cache warming completed')

  } catch (error) {
    this.logger.error('Cache warming failed', { error })
  }
}

private async warmPopularQueries(): Promise<void> {
  // Get popular queries from analytics
  const popularQueries = await this.redisClient.zrevrange('popular:queries', 0, 99, 'WITHSCORES')

  // Preload search results for popular queries
  for (let i = 0; i < popularQueries.length; i += 2) {
    const query = popularQueries[i]
    const score = popularQueries[i + 1]

    // Only warm queries with significant usage
    if (parseFloat(score) > 10) {
      await this.preloadSearchResults(query)
    }
  }
}

Best Practices and Configuration

Performance Optimization

  1. TTL Strategy: Use appropriate TTLs based on data volatility
  2. Key Design: Use consistent, hierarchical key naming patterns
  3. Batch Operations: Group multiple operations when possible
  4. Memory Management: Monitor memory usage and implement eviction policies
  5. Connection Pooling: Use connection pooling for high-throughput scenarios

Security Best Practices

  1. Access Control: Implement proper Redis authentication
  2. Network Security: Use TLS encryption for Redis connections
  3. Key Isolation: Use key prefixes to isolate different applications
  4. Session Security: Implement secure session management practices

The CacheService provides the performance foundation for Altus 4, delivering fast response times and scalable analytics storage through intelligent Redis-based caching strategies.