--- name: caching-patterns description: Caching strategies and patterns for GoodGo microservices including multi-layer cache, Redis caching, cache key naming, TTL strategies, cache invalidation, and cache-aside patterns. --- # Caching Patterns ## When to Use This Skill Use this skill when: - Implementing caching for frequently accessed data - Optimizing database queries with caching - Designing cache key naming conventions - Setting TTL (Time To Live) strategies - Implementing cache invalidation patterns - Using multi-layer cache (L1: Memory, L2: Redis) - Handling cache failures gracefully ## Core Concepts ### Multi-Layer Cache Strategy The platform uses a two-layer cache architecture to balance speed and capacity: ```mermaid graph TB subgraph Application["Application Layer"] App[Application Code] end subgraph L1Layer["L1 Cache - Memory (NodeCache)"] L1[In-Memory Cache] L1Props["• Speed: < 1ms
• Capacity: 10k keys
• TTL: 60s-5min
• Scope: Per-instance"] end subgraph L2Layer["L2 Cache - Redis (Distributed)"] L2[Redis Cache] L2Props["• Speed: < 5ms
• Capacity: Large
• TTL: Configurable
• Scope: Shared"] end subgraph DataLayer["Data Source"] DB[(Database)] API[External API] end App -->|Check First| L1 L1 -->|Miss| L2 L2 -->|Miss| DB L2 -->|Miss| API DB -->|Store| L2 API -->|Store| L2 L2 -->|Warm| L1 L1 -.-> L1Props L2 -.-> L2Props style L1 fill:#e1f5ff style L2 fill:#fff4e1 style DB fill:#ffe1e1 style API fill:#ffe1e1 ``` **Layer Characteristics:** 1. **L1 Cache (Memory)**: NodeCache in-memory cache - Very fast (< 1ms access time) - Limited capacity (10k keys default) - Short TTL (60 seconds default, max 5 minutes) - Per-instance (not shared across instances) 2. **L2 Cache (Redis)**: Distributed Redis cache - Fast (< 5ms access time) - Large capacity - Longer TTL (configurable) - Shared across all service instances ### Cache Flow The cache lookup follows a multi-layer approach, checking L1 first, then L2, and finally the data source. ```mermaid flowchart TD Start([Request Data]) --> CheckL1{Check L1 Cache
Memory} CheckL1 -->|Hit| ReturnL1[Return Data
from L1] CheckL1 -->|Miss| CheckL2{Check L2 Cache
Redis} CheckL2 -->|Hit| StoreL1[Store in L1
Warm Cache] StoreL1 --> ReturnL2[Return Data
from L2] CheckL2 -->|Miss| FetchSource[Fetch from
Data Source] FetchSource --> StoreBoth[Store in L1 & L2] StoreBoth --> ReturnSource[Return Data
from Source] ReturnL1 --> End([End]) ReturnL2 --> End ReturnSource --> End style CheckL1 fill:#e1f5ff style CheckL2 fill:#fff4e1 style FetchSource fill:#ffe1e1 style ReturnL1 fill:#e1ffe1 style ReturnL2 fill:#e1ffe1 style ReturnSource fill:#e1ffe1 ``` ## Patterns ### Cache Service Usage ```typescript import { cacheService } from '../core/cache'; // Simple get/set const cached = await cacheService.get('user:123'); await cacheService.set('user:123', userData, 300); // 5 minutes TTL // Get or set pattern (cache-aside) const user = await cacheService.getOrSet( 'user:123', async () => { return await userRepository.findById('123'); }, 300 // TTL in seconds ); ``` ### Cache Key Naming Conventions Use consistent naming patterns: ```typescript // Pattern: {entity}:{identifier} 'user:123' 'user:email:user@example.com' 'user:123:permissions' 'user:123:roles' // Pattern: {entity}:{identifier}:{sub-resource} 'session:abc123' 'permission:perm_123' 'role:role_123' ``` Cache service provides key generators: ```typescript cacheService.keys = { user: (userId: string) => `user:${userId}`, userPermissions: (userId: string) => `user:${userId}:permissions`, userRoles: (userId: string) => `user:${userId}:roles`, token: (token: string) => `token:${token}`, session: (sessionId: string) => `session:${sessionId}`, }; ``` ### Cache-Aside Pattern Most common pattern - check cache first, fetch if miss: ```typescript async getUserPermissions(userId: string): Promise { const cacheKey = cacheService.keys.userPermissions(userId); // Try cache first const cached = await cacheService.get(cacheKey); if (cached) { return cached; } // Cache miss - fetch from source const permissions = await calculatePermissions(userId); // Store in cache await cacheService.set(cacheKey, permissions, 300); // 5 min TTL return permissions; } ``` ### Get or Set Pattern Simplified cache-aside pattern: ```typescript const permissions = await cacheService.getOrSet( cacheService.keys.userPermissions(userId), async () => { // This function only runs on cache miss return await calculatePermissions(userId); }, 300 // TTL ); ``` ### TTL Strategies Choose TTL based on data characteristics: **Short TTL (60-300s)**: Frequently changing data - User permissions (300s) - Session data (varies) - Real-time statistics **Medium TTL (300-1800s)**: Moderately changing data - User profiles (600s) - Organization data (900s) - Configuration (1800s) **Long TTL (1800-3600s)**: Rarely changing data - Static configurations (3600s) - Reference data (7200s) **No TTL**: Very stable data (use with caution) - Rarely use - prefer long TTL instead ### Cache Invalidation Invalidate cache when data changes to prevent serving stale data. The platform supports multiple invalidation strategies: ```mermaid flowchart TD Start([Data Changed]) --> ChooseStrategy{Choose
Invalidation
Strategy} ChooseStrategy -->|Single Key| SingleKey[Single Key
Invalidation] SingleKey --> DelL1[Delete from L1] DelL1 --> DelL2[Delete from L2] DelL2 --> Done1([Complete]) ChooseStrategy -->|Pattern Match| PatternMatch[Pattern-Based
Invalidation] PatternMatch --> FindKeys[Find Matching Keys
user:123:*] FindKeys --> DelManyL1[Delete from L1
All Matching] DelManyL1 --> DelManyL2[Delete from L2
All Matching] DelManyL2 --> Done2([Complete]) ChooseStrategy -->|Multiple Keys| MultipleKeys[Multiple Keys
Invalidation] MultipleKeys --> ListKeys[List Keys to Delete
user:123
user:123:permissions
user:123:roles] ListKeys --> BatchDelL1[Batch Delete from L1] BatchDelL1 --> BatchDelL2[Batch Delete from L2] BatchDelL2 --> Done3([Complete]) style SingleKey fill:#e1f5ff style PatternMatch fill:#fff4e1 style MultipleKeys fill:#ffe1e1 style Done1 fill:#e1ffe1 style Done2 fill:#e1ffe1 style Done3 fill:#e1ffe1 ``` **Implementation Examples:** ```typescript // Single key invalidation await cacheService.del(cacheService.keys.user(userId)); await cacheService.del(cacheService.keys.userPermissions(userId)); // Pattern-based invalidation await cacheService.invalidatePattern('user:123:*'); // Multiple keys await cacheService.delMany([ cacheService.keys.user(userId), cacheService.keys.userPermissions(userId), cacheService.keys.userRoles(userId), ]); ``` ### Cache Warming Pre-populate cache with frequently accessed data: ```typescript async warmCache() { const activeUsers = await userRepository.findActiveUsers(); for (const user of activeUsers) { // Pre-cache user data await cacheService.set( cacheService.keys.user(user.id), user, 600 ); // Pre-cache permissions const permissions = await calculatePermissions(user.id); await cacheService.set( cacheService.keys.userPermissions(user.id), permissions, 300 ); } } ``` ### Error Handling Cache failures should not break the application: ```typescript async getWithCache(key: string): Promise { try { // Try cache first const cached = await cacheService.get(key); if (cached) return cached; } catch (error) { // Log but continue - fallback to source logger.warn('Cache get failed, falling back to source', { key, error }); } // Fallback to data source return await fetchFromSource(); } ``` ## Best Practices 1. **Cache Keys**: Use consistent naming conventions 2. **TTL Selection**: Choose TTL based on data change frequency 3. **Invalidation**: Invalidate cache when data changes 4. **Error Handling**: Don't let cache failures break the app 5. **Cache-Aside**: Use cache-aside pattern for most cases 6. **Avoid Over-Caching**: Don't cache data that changes too frequently 7. **Monitor Hit Rates**: Track cache hit rates to optimize TTL 8. **Warm Cache**: Pre-populate cache for critical data 9. **Use Multi-Layer**: Leverage both L1 and L2 cache 10. **Serialize Properly**: Ensure data is JSON serializable ## Common Mistakes 1. **Cache Key Collisions**: Using generic keys that collide 2. **Stale Data**: Not invalidating cache when data changes 3. **Too Short TTL**: Setting TTL too short, negating cache benefits 4. **Too Long TTL**: Setting TTL too long, serving stale data 5. **No Error Handling**: Letting cache errors break the application 6. **Caching Everything**: Caching data that doesn't benefit from caching 7. **Not Warming Cache**: Not pre-populating critical cache data 8. **Ignoring Hit Rates**: Not monitoring cache performance ## Troubleshooting ### Low Cache Hit Rate **Problem**: Cache hit rate is low, cache not effective **Solution**: - Review TTL values - may be too short - Check cache key patterns - ensure consistent usage - Verify cache invalidation isn't too aggressive - Monitor what data is being cached ### Stale Data Issues **Problem**: Serving stale data from cache **Solution**: - Review TTL values - may be too long - Ensure cache invalidation on data updates - Use shorter TTL for frequently changing data - Implement cache versioning if needed ### Cache Performance Issues **Problem**: Cache operations are slow **Solution**: - Check Redis connection and network latency - Monitor Redis memory usage - Review cache key patterns for efficiency - Consider L1 cache hit rate (should be high) ## Resources - [Multi-Layer Cache](../../services/iam-service/src/core/cache/multi-layer-cache.ts) - Multi-layer cache implementation - [Cache Service](../../services/iam-service/src/core/cache/cache.service.ts) - Cache service wrapper - [Cache Usage Example](../../services/iam-service/src/modules/rbac/rbac.service.ts) - Real-world cache usage