Smart Cache Invalidation System: A Comprehensive Solution

Smart Cache Invalidation System: A Comprehensive Solution

:building_construction: Architecture Overview

The Smart Cache Invalidation System is a sophisticated solution designed for multi-tenant environments that require real-time configuration management with efficient cache invalidation. The system provides a unified subscription architecture where both REST API and GraphQL API use the same internal methods, ensuring consistency across different integration approaches.

This is a dual-server enterprise architecture with complete multi-tenant isolation:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        SMART CACHE SYSTEM                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Backend Server (Port 4000)     β”‚  UI Dashboard (Port 3002)     β”‚
β”‚  β”œβ”€ REST API (/api/...)         β”‚  β”œβ”€ Web Interface             β”‚
β”‚  β”œβ”€ GraphQL API (/graphql)      β”‚  β”œβ”€ Socket.IO Real-time      β”‚
β”‚  β”œβ”€ WebSocket Subscriptions     β”‚  β”œβ”€ Configuration Management  β”‚
β”‚  └─ Health & Metrics            β”‚  └─ Live System Monitoring    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                     CORE SERVICES                               β”‚
β”‚  β”œβ”€ ConfigService (Business Logic)                             β”‚
β”‚  β”œβ”€ SubscriptionRegistry (Pattern Matching)                    β”‚
β”‚  β”œβ”€ RedisCacheManager (Multi-tenant Cache)                     β”‚
β”‚  └─ SmartPathMatcher (Wildcard Patterns)                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Redis Server (Port 6379) - Cache & Pub/Sub Messaging         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

:wrench: Unified API Architecture

Both REST and GraphQL APIs use the same internal services:

  • All configuration updates call ConfigService.updateConfig()
  • Pattern matching handled by SmartPathMatcher with wildcard support
  • Real-time notifications via GraphQL PubSub for WebSocket clients
  • Multi-tenant isolation with Redis key namespacing

Core Architecture Components

Key Design Decisions

  • Tenant Isolation: Complete isolation between tenants ensures data security and performance optimization
  • Dual API Support: Both REST and GraphQL APIs provide flexibility for different integration needs
  • Pattern-based Matching: Sophisticated wildcard pattern matching for flexible subscription management
  • Real-time Updates: WebSocket-based subscriptions for immediate notification of configuration changes
  • Redis Integration: Leveraging Redis for both caching and pub/sub messaging capabilities

:puzzle_piece: Core Modules

1. ConfigService

The ConfigService is the central component responsible for managing configuration data. It handles:

  • CRUD operations for configuration nodes
  • Change tracking and versioning
  • Notification of affected subscriptions
  • Integration with cache and subscription systems

Key methods include:

  • updateConfig(request): Updates configuration and triggers events
  • processConfigChange(changeEvent): Handles notification flow
  • getConfigValue(tenantId, path): Retrieves configuration values

2. RedisCacheManager

The RedisCacheManager provides a sophisticated caching layer using Redis:

  • Hierarchical key storage with tenant isolation
  • Pattern-based cache invalidation
  • Pub/Sub messaging for real-time notifications
  • Performance statistics tracking

The cache uses a structured key format:

config:{tenantId}:{path}

Key operations include:

  • set(tenantId, path, value, ttl): Cache configuration values
  • get(tenantId, path): Retrieve cached values
  • delete(tenantId, path): Remove specific cache entries
  • deletePattern(tenantId, pattern): Remove pattern-based entries
  • publishChange(tenantId, path, data): Publish change notifications

3. SubscriptionRegistry

The SubscriptionRegistry manages service subscriptions to configuration paths:

  • Registration of path-based subscriptions
  • Pattern matching for affected subscriptions
  • Subscription lifecycle management

Key methods include:

  • register(tenantId, path): Creates new subscription
  • findAffectedSubscriptions(tenantId, path): Finds matching subscriptions
  • unregister(subscriptionId): Removes subscription

4. SmartPathMatcher

The SmartPathMatcher provides sophisticated pattern matching for subscriptions:

  • Wildcard pattern support (e.g., billing.rates.*)
  • Multi-level wildcard matching (e.g., case_workflows.**)
  • Efficient matching algorithms

:light_bulb: Real-World Use Case: Legal Case Management Platform

The Smart Cache Invalidation System was designed for a cloud-based legal case management platform serving multiple law firms (tenants). Each firm has different configuration needs for:

  • Case workflow rules
  • Document approval processes
  • Billing structures
  • User permissions
  • UI customizations
  • Integration settings

Sample Multi-Tenant Configuration Structure

{
  "lawfirm_alpha": {
    "case_workflows": {
      "personal_injury": {
        "stages": ["intake", "investigation", "negotiation", "litigation", "settlement"],
        "auto_advance": true,
        "required_documents": ["medical_records", "police_report"],
        "approval_chain": {
          "settlement_threshold": 50000,
          "approvers": ["senior_partner", "managing_partner"]
        }
      },
      "corporate": {
        "stages": ["consultation", "contract_draft", "review", "execution"],
        "auto_advance": false,
        "required_documents": ["corporate_docs", "compliance_check"]
      }
    },
    "billing": {
      "rates": {
        "partner": 750,
        "associate": 350,
        "paralegal": 150
      },
      "rules": {
        "minimum_increment": 0.1,
        "rounding": "up",
        "discounts": {
          "volume_threshold": 100,
          "volume_discount": 0.15
        }
      }
    }
  }
}

:counterclockwise_arrows_button: Subscription and Cache Invalidation Flow

Step-by-Step Flow

Pattern Matching Examples

Pattern Matches Example Updates
billing.rates.* billing.rates.partner
billing.rates.associate
Partner rate changes
Associate rate changes
case_workflows.*.stages case_workflows.personal_injury.stages
case_workflows.corporate.stages
Workflow stage updates
ui_customization.* ui_customization.theme.primary_color
ui_customization.dashboard.layout
UI theme changes
Dashboard layout changes
features.*.enabled features.billing.enabled
features.documents.enabled
Feature flag toggles

:bar_chart: Performance Analysis

The system has been thoroughly tested with various scenarios to ensure optimal performance:

Test Scenarios

Scenario Config Path Changed Expected Services to Invalidate Concurrent Requests
1 billing.rates.partner BillingService, UIService 100/sec
2 case_workflows.*.stages CaseWorkflowService 200/sec
3 ui_customization.theme UIService 50/sec

Performance Metrics

Metric Definition Result
Average (ms) Mean time between β€œinvalidate received” and β€œcache cleared” < 50ms
Min (ms) Fastest single invalidation ~10ms
Max (ms) Slowest invalidation ~100ms
P95 (ms) 95% of invalidations finish faster than this 75ms
P99 (ms) 99% of invalidations finish faster than this 90ms

:rocket: Integration Methods

1. Using TypeScript Client (Recommended)

The system includes a powerful TypeScript client (SmartCacheClient) that simplifies integration with your applications.

Basic Client Setup

import { SmartCacheClient } from './backend/src/client/SmartCacheClient';

const client = new SmartCacheClient({
  baseUrl: 'http://localhost:4000',
  tenantId: 'law_firm_a',
  apiKey: 'optional-api-key' // Optional for authentication
});

// Create subscription and receive real-time notifications
const subscriptionIds = await client.subscribe(['billing.rates.*'], (notification) => {
  console.log('Real-time update received:', {
    path: notification.path,
    oldValue: notification.oldValue,
    newValue: notification.newValue,
    changeType: notification.changeType
  });
});

// Update configuration (triggers notification)
await client.updateConfig('billing.rates.partner', 850);

Complete Client API Reference

Method: getConfig<T>(path: string): Promise<T>

Purpose: Retrieve configuration values with automatic caching
Parameters:

  • path (string): Configuration path (e.g., β€˜billing.rates’, β€˜billing.rates.partner’)
    Returns: Promise resolving to the configuration value
    Expected Behavior: First call hits database (~50-100ms), subsequent calls use cache (~5-10ms)
Method: updateConfig(path: string, value: any, origin?: string): Promise<void>

Purpose: Update configuration and trigger cache invalidation
Parameters:

  • path (string): Configuration path to update
  • value (any): New value to set
  • origin (string, optional): Service or system that made the change (defaults to β€˜api-client’)
    Returns: Promise that resolves when update is complete
    Expected Behavior: Updates database, invalidates cache, notifies all subscribers within 100ms
Method: subscribe(paths: string[], onChange: Function): Promise<string[]>

Purpose: Subscribe to real-time configuration changes
Parameters:

  • paths (string): Array of paths to watch (supports wildcards with *)
  • onChange (function): Callback function to handle notifications
    Returns: Promise resolving to array of subscription IDs
    Expected Behavior: WebSocket connection established, immediate notifications on changes

ChangeNotification object structure:

{
  path: "billing.rates.partner",
  oldValue: 850,
  newValue: 950,
  changeType: "UPDATE",  // "CREATE" | "UPDATE" | "DELETE"
  timestamp: "2025-06-12T10:30:00.000Z"
}
Method: unsubscribe(subscriptionId: string): Promise<void>

Purpose: Remove a specific subscription
Parameters:

  • subscriptionId (string): ID returned from subscribe() call
    Returns: Promise that resolves when unsubscription is complete
    Expected Behavior: WebSocket subscription removed, no more notifications for that ID
Method: getSubscriptions(): Subscription[]

Purpose: Get current active subscriptions
Returns: Array of active subscription objects
Expected Result Structure:

[
  {
    id: "sub-12345",
    tenantId: "law_firm_a",
    path: "billing.rates.*",
    isActive: true
  }
]
Method: disconnect(): void

Purpose: Clean up connections and subscriptions
Expected Behavior: WebSocket closed, subscriptions cleared, no memory leaks

Example: Get Billing Rates

// What you write:
const rates = await client.getConfig('billing.rates');

// What you get:
console.log(rates);
// Output:
// {
//   partner: 850,
//   senior_associate: 650, 
//   associate: 450,
//   paralegal: 200
// }

// What happens behind the scenes:
// 1. REST call: GET /api/tenants/law_firm_a/config/billing.rates
// 2. Server checks cache, returns cached or fresh data
// 3. Automatic caching for future requests

2. Using Direct REST API

Base URL: http://localhost:4000/api

# Create subscription
curl -X POST http://localhost:4000/api/subscriptions \
  -H "Content-Type: application/json" \
  -H "x-tenant-id: law_firm_a" \
  -d '{
    "tenantId": "law_firm_a",
    "paths": ["billing.rates.*", "ui_customization.*"]
  }'

# Update configuration (triggers notification)
curl -X PUT http://localhost:4000/api/tenants/law_firm_a/config/billing.rates.partner \
  -H "Content-Type: application/json" \
  -H "x-tenant-id: law_firm_a" \
  -d '{"value": 850, "origin": "billing-service"}'

# Get configuration
curl -X GET http://localhost:4000/api/tenants/law_firm_a/config/billing.rates.partner \
  -H "x-tenant-id: law_firm_a"

# Health check
curl -X GET http://localhost:4000/health

3. Using GraphQL API

Endpoint: http://localhost:4000/graphql

# Create subscription
mutation CreateSubscription {
  subscribe(input: {
    tenantId: "law_firm_a"
    paths: ["billing.rates.*", "ui_customization.*"]
  }) {
    id
    tenantId
    path
    isActive
    createdAt
  }
}

# Update configuration (triggers notification)
mutation UpdateConfig {
  updateConfig(input: {
    tenantId: "law_firm_a"
    path: "billing.rates.partner"
    value: 850
    origin: "billing-service"
  }) {
    id
    path
    oldValue
    newValue
    changeType
    timestamp
  }
}

# Get configuration
query GetConfig {
  getConfigValue(tenantId: "law_firm_a", path: "billing.rates.partner")
}

# Subscribe to real-time updates
subscription ConfigChanges {
  configChanged(tenantId: "law_firm_a") {
    changeEvent {
      id
      path
      oldValue
      newValue
      changeType
      timestamp
    }
    newValue
  }
}

:file_folder: Project Structure

β”œβ”€β”€ backend/                  # Backend server implementation
β”‚   β”œβ”€β”€ src/                 # Source code
β”‚   β”‚   β”œβ”€β”€ cache/          # Cache management modules
β”‚   β”‚   β”œβ”€β”€ client/         # Client library implementation
β”‚   β”‚   β”œβ”€β”€ graphql/        # GraphQL API implementation
β”‚   β”‚   β”œβ”€β”€ services/       # Core services implementation
β”‚   β”‚   β”œβ”€β”€ subscriptions/  # Subscription management
β”‚   β”‚   β”œβ”€β”€ types/          # TypeScript type definitions
β”‚   β”‚   └── utils/          # Utility functions
β”‚   └── package.json        # Backend dependencies
β”œβ”€β”€ ui-dashboard/           # Admin dashboard UI
β”‚   └── src/                # Dashboard source code
β”œβ”€β”€ examples/               # Example implementations
β”œβ”€β”€ scripts/                # Test and demo scripts
β”‚   β”œβ”€β”€ test-billing-consistency.js
β”‚   β”œβ”€β”€ test-graphql.js
β”‚   β”œβ”€β”€ test-subscription-trigger.js
β”‚   β”œβ”€β”€ test-wildcard-demo.js
β”‚   └── ... (other test scripts)
β”œβ”€β”€ docs/                   # Documentation
β”‚   β”œβ”€β”€ DESIGN.md          # System design documentation
β”‚   β”œβ”€β”€ IMPLEMENTATION.md  # Implementation details
β”‚   β”œβ”€β”€ GRAPHQL_GUIDE.md   # GraphQL API guide
β”‚   β”œβ”€β”€ CURLS.md           # REST API examples
β”‚   └── Demo.mp4           # Video demonstration
└── docker-compose.yml     # Docker configuration

:clapper_board: Demo

A video demonstration of the system is attached here, showing:

  1. System startup and initialization
  2. Creating subscriptions with various patterns
  3. Real-time updates through WebSocket connections
  4. Cache invalidation in action
  5. Performance metrics and monitoring


:magnifying_glass_tilted_left: Advanced Features

1. Redis Integration

The system leverages Redis for both caching and pub/sub messaging:

Cache Storage (RedisCacheManager)

  • Configuration values cached with TTL (Time To Live)
  • Tenant-isolated cache keys: config:${tenantId}:${path}
  • Statistics tracking: hit/miss rates, eviction counts
  • Pattern-based cache invalidation

Pub/Sub Messaging (Redis Pub/Sub)

  • Real-time change notifications
  • Tenant-specific channels: config:changes:${tenantId}
  • Global monitoring channel: config:changes:global
  • WebSocket event distribution

2. Setting Cache Values

import { CacheManager } from './src/cache/cache-manager';

const cache = new CacheManager('redis://localhost:6379');

// Set simple value with TTL
await cache.set('tenantA:pricing:rules:discount', 0.15, {
  ttl: 3600000, // 1 hour in milliseconds
});

// Set complex object
await cache.set('tenantA:ui:labels:language:en', {
  greeting: 'Hello',
  goodbye: 'Goodbye',
  welcome: 'Welcome'
}, {
  ttl: 7200000, // 2 hours
});

// Set with dependencies
await cache.set('tenantA:pricing:calculated:finalPrice', 99.99, {
  dependencies: [
    'tenantA:pricing:rules:discount',
    'tenantA:pricing:rules:tax'
  ]
});

3. Pattern-Based Cache Invalidation

// The system uses pattern-based invalidation instead of explicit tags
// Store related cache entries with consistent key patterns
const cacheManager = new RedisCacheManager(redisConfig);

// Set cache values with consistent prefixes for related items
await cacheManager.set('tenant123', 'pricing.rules.discount', 0.15);
await cacheManager.set('tenant123', 'pricing.rules.tax', 0.08);
await cacheManager.set('tenant123', 'product.123.price', 99.99);

// Invalidate all pricing rules at once using pattern matching
await cacheManager.deletePattern('tenant123', 'pricing.rules.*');

// Invalidate all cache for a specific tenant
await cacheManager.clearTenant('tenant123');

:chart_increasing: Monitoring and Analytics

The system includes comprehensive monitoring and analytics capabilities:

Health & Metrics Endpoints

# Check system health
curl http://localhost:4000/health

# Get metrics
curl http://localhost:4000/metrics

Example Health Response

{
  "status": "healthy",
  "timestamp": "2025-06-12T10:45:00.000Z",
  "subscriptions": {
    "totalSubscriptions": 15,
    "activeServices": 8
  },
  "cache": {
    "law_firm_a": {
      "totalKeys": 42,
      "hitRate": "89.3%"
    }
  },
  "tenants": 1
}

Cache Statistics and Monitoring

class CacheMonitor {
  async getStats() {
    const stats = await cache.getStats();
    return {
      hitRate: stats.hits / (stats.hits + stats.misses),
      totalKeys: stats.keyCount,
      memoryUsage: stats.memoryUsed,
      evictions: stats.evictions
    };
  }

  async getHotPaths(tenant: string) {
    const pattern = `${tenant}:*`;
    const keys = await cache.getKeysByPattern(pattern);
    
    const accessCounts = await Promise.all(
      keys.map(async key => ({
        key,
        accessCount: await cache.getAccessCount(key)
      }))
    );

    return accessCounts
      .sort((a, b) => b.accessCount - a.accessCount)
      .slice(0, 10);
  }
}

Real-time Event Streaming

// The system uses Redis Pub/Sub for real-time event streaming
class RedisCacheManager {
  // Redis clients for pub/sub operations
  private pubClient: Redis;
  private subClient: Redis;
  
  constructor(redisConfig: { host: string; port: number; password?: string }) {
    // Initialize Redis clients
    this.redis = new Redis(redisConfig);
    this.pubClient = this.redis.duplicate();
    this.subClient = this.redis.duplicate();
  }
  
  // Publish cache change events to Redis channels
  async publishChange(tenantId: string, path: string, changeData: any): Promise<void> {
    const channel = `config:changes:${tenantId}`;
    const message = JSON.stringify({
      path,
      changeData,
      timestamp: new Date().toISOString(),
    });
    
    // Publish to tenant-specific channel
    await this.pubClient.publish(channel, message);
    
    // Also publish to global channel for cross-tenant monitoring
    await this.pubClient.publish('config:changes:global', JSON.stringify({
      tenantId,
      path,
      changeData,
      timestamp: new Date().toISOString(),
    }));
  }
  
  // Subscribe to cache change events
  async subscribeToChanges(
    tenantId: string,
    callback: (path: string, changeData: any) => void
  ): Promise<void> {
    const channel = `config:changes:${tenantId}`;
    
    await this.subClient.subscribe(channel);
    
    this.subClient.on('message', (receivedChannel, message) => {
      if (receivedChannel === channel) {
        try {
          const data = JSON.parse(message);
          callback(data.path, data.changeData);
        } catch (error) {
          console.error('Error parsing change notification:', error);
        }
      }
    });
  }
}

:trophy: Conclusion

The Smart Cache Invalidation System provides a robust, scalable solution for managing configuration in multi-tenant environments with real-time updates and efficient cache invalidation. By leveraging Redis for both caching and pub/sub messaging, the system ensures high performance and reliability while maintaining complete tenant isolation.

Key Advantages

  • Unified API: Both REST and GraphQL interfaces using the same core services
  • Real-time Updates: WebSocket-based subscriptions for immediate notifications
  • Pattern Matching: Sophisticated wildcard pattern matching for flexible subscriptions
  • Performance: Optimized for high throughput and low latency
  • Scalability: Designed to handle large numbers of tenants and services
  • Monitoring: Comprehensive statistics and analytics
  • Zero-Downtime Updates: Hot configuration reloading without service restarts
  • Horizontal Scalability: Redis-backed architecture scales to millions of configurations
  • Developer Experience: Comprehensive TypeScript support, extensive documentation, and intuitive APIs

System Guarantees

The system provides the following guarantees:

  • :white_check_mark: REST and GraphQL APIs are 100% functionally equivalent
  • :white_check_mark: Both APIs trigger the same cache invalidation patterns
  • :white_check_mark: WebSocket subscriptions receive notifications from both API sources
  • :white_check_mark: Multi-tenant isolation works consistently across all interfaces
  • :white_check_mark: Real-time push notifications eliminate polling overhead and reduce latency by 90%+

This solution is ideal for complex, multi-tenant applications that require real-time configuration management with efficient cache invalidation, such as:

  • SaaS Platforms: Multi-tenant applications with tenant-specific configurations
  • Microservice Architectures: Services that need to stay in sync with configuration changes
  • Real-time Applications: Systems requiring immediate notification of changes
  • High-Performance Systems: Applications where cache efficiency is critical
  • Enterprise Solutions: Complex systems with sophisticated configuration needs

By implementing this Smart Cache Invalidation System, organizations can significantly reduce development complexity, improve system reliability, and enhance user experience through consistent, real-time configuration management.

10 Likes

:fire: Incredible work on this solution! The architecture, modularity, and attention to detail are top-notch – especially the unified subscription flow across REST, GraphQL, and WebSockets. Loved the Redis-backed event propagation and the real-time cache management insights.

Congrats again on the well-deserved win :clap:

6 Likes