326 lines
8.2 KiB
Markdown
326 lines
8.2 KiB
Markdown
---
|
|
trigger: always_on
|
|
---
|
|
|
|
# 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:
|
|
|
|
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
|
|
|
|
```
|
|
Request → L1 Cache → Hit? Return
|
|
↓ Miss
|
|
L2 Cache → Hit? Return + Warm L1
|
|
↓ Miss
|
|
Data Source (DB/API) → Store in L1 & L2 → Return
|
|
```
|
|
|
|
## Patterns
|
|
|
|
### Cache Service Usage
|
|
|
|
```typescript
|
|
import { cacheService } from '../core/cache';
|
|
|
|
// Simple get/set
|
|
const cached = await cacheService.get<User>('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<string[]> {
|
|
const cacheKey = cacheService.keys.userPermissions(userId);
|
|
|
|
// Try cache first
|
|
const cached = await cacheService.get<string[]>(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:
|
|
|
|
```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<Data | null> {
|
|
try {
|
|
// Try cache first
|
|
const cached = await cacheService.get<Data>(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)
|
|
|
|
## Quick Reference
|
|
|
|
| Cache Layer | Speed | Capacity | TTL | Scope |
|
|
|-------------|-------|----------|-----|-------|
|
|
| **L1 (Memory)** | <1ms | 10k keys | 60s-5min | Per instance |
|
|
| **L2 (Redis)** | <5ms | Large | Configurable | Shared |
|
|
|
|
**TTL Guidelines:**
|
|
| Data Type | TTL | Example |
|
|
|-----------|-----|---------|
|
|
| Session data | 15-60min | User sessions |
|
|
| Permissions | 5min | RBAC cache |
|
|
| User profiles | 10min | Profile data |
|
|
| Config | 30min | Feature flags |
|
|
| Static data | 1-2hr | Reference data |
|
|
|
|
**Key Patterns:**
|
|
```typescript
|
|
// Entity
|
|
`user:${userId}`
|
|
`session:${sessionId}`
|
|
|
|
// Entity + Sub-resource
|
|
`user:${userId}:permissions`
|
|
`user:${userId}:roles`
|
|
|
|
// List/Collection
|
|
`users:list:page:${page}`
|
|
`products:category:${categoryId}`
|
|
```
|
|
|
|
**Essential Operations:**
|
|
```typescript
|
|
// Get/Set
|
|
await cache.get<T>(key);
|
|
await cache.set(key, value, ttl);
|
|
|
|
// Get or fetch
|
|
await cache.getOrSet(key, fetchFn, ttl);
|
|
|
|
// Invalidate
|
|
await cache.del(key);
|
|
await cache.invalidatePattern('user:123:*');
|
|
```
|
|
|
|
## 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
|