---
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