- Updated skill documentation files to include structured metadata for better organization. - Enhanced bilingual descriptions and guidelines for clarity in both English and Vietnamese. - Refined sections on usage, best practices, and related skills to ensure consistency across all documentation. - Improved formatting and removed outdated references to streamline the documentation experience. - Added best practices checklists to relevant skills for better usability and adherence to standards.
282 lines
7.4 KiB
Markdown
282 lines
7.4 KiB
Markdown
---
|
|
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:
|
|
|
|
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)
|
|
|
|
## 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
|