- 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.
7.4 KiB
name, description
| name | description |
|---|---|
| caching-patterns | 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:
-
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)
-
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
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:
// 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:
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:
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:
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:
// 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:
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:
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
- Cache Keys: Use consistent naming conventions
- TTL Selection: Choose TTL based on data change frequency
- Invalidation: Invalidate cache when data changes
- Error Handling: Don't let cache failures break the app
- Cache-Aside: Use cache-aside pattern for most cases
- Avoid Over-Caching: Don't cache data that changes too frequently
- Monitor Hit Rates: Track cache hit rates to optimize TTL
- Warm Cache: Pre-populate cache for critical data
- Use Multi-Layer: Leverage both L1 and L2 cache
- Serialize Properly: Ensure data is JSON serializable
Common Mistakes
- Cache Key Collisions: Using generic keys that collide
- Stale Data: Not invalidating cache when data changes
- Too Short TTL: Setting TTL too short, negating cache benefits
- Too Long TTL: Setting TTL too long, serving stale data
- No Error Handling: Letting cache errors break the application
- Caching Everything: Caching data that doesn't benefit from caching
- Not Warming Cache: Not pre-populating critical cache data
- 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 - Multi-layer cache implementation
- Cache Service - Cache service wrapper
- Cache Usage Example - Real-world cache usage