--- name: redis-caching description: Redis caching patterns cho distributed systems. Use for cache-aside, session storage, rate limiting, và distributed locks. compatibility: ".NET 8+, StackExchange.Redis, Microsoft.Extensions.Caching.StackExchangeRedis" metadata: author: Velik Ho version: "1.0" --- # Redis Caching Patterns / Mẫu Caching Redis Redis caching và distributed data patterns cho GoodGo microservices. ## When to Use This Skill / Khi Nào Sử Dụng Use this skill when: - Implementing distributed cache / Triển khai distributed cache - Caching API responses / Caching responses API - Managing user sessions / Quản lý user sessions - Implementing rate limiting / Triển khai rate limiting - Using Redis as primary store (e.g., shopping cart) / Dùng Redis làm store chính ## Core Concepts / Khái Niệm Cốt Lõi ### Caching Strategies / Các Chiến Lược Caching ``` ┌─────────────────────────────────────────────────────────────┐ │ CACHING PATTERNS │ ├─────────────────────┬─────────────────────┬─────────────────┤ │ CACHE-ASIDE │ WRITE-THROUGH │ WRITE-BEHIND │ │ (Read pattern) │ (Write pattern) │ (Async write) │ ├─────────────────────┼─────────────────────┼─────────────────┤ │ 1. Check cache │ 1. Write to cache │ 1. Write cache │ │ 2. If miss, get DB │ 2. Write to DB sync │ 2. Queue DB │ │ 3. Populate cache │ │ 3. Async flush │ └─────────────────────┴─────────────────────┴─────────────────┘ ``` ### Redis Data Types / Các Kiểu Dữ Liệu Redis | Type | Use Case | Example | |------|----------|---------| | **String** | Simple cache | User profile, JWT token | | **Hash** | Object cache | Shopping cart | | **List** | Queues | Task queue | | **Set** | Unique items | Active users | | **Sorted Set** | Rankings | Leaderboard | ### Cache Invalidation / Xóa Cache | Strategy | Description | Use When | |----------|-------------|----------| | **TTL** | Auto-expire after time | Data changes rarely | | **Event-based** | Invalidate on update | Data changes often | | **Version key** | Cache with version | Complex objects | ## Key Patterns / Mẫu Chính ### Redis Configuration ```csharp /// /// EN: Configure Redis distributed cache. /// VI: Cấu hình Redis distributed cache. /// // Program.cs builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = builder.Configuration["Redis:ConnectionString"]; options.InstanceName = "GoodGo:"; }); // EN: Register Redis connection multiplexer for advanced scenarios // VI: Đăng ký Redis connection multiplexer cho các trường hợp nâng cao builder.Services.AddSingleton(sp => ConnectionMultiplexer.Connect(builder.Configuration["Redis:ConnectionString"]!)); ``` ### Cache Service Implementation ```csharp /// /// EN: Generic cache service with typed operations. /// VI: Cache service generic với typed operations. /// public interface ICacheService { Task GetAsync(string key, CancellationToken ct = default); Task SetAsync(string key, T value, TimeSpan? expiry = null, CancellationToken ct = default); Task RemoveAsync(string key, CancellationToken ct = default); Task GetOrSetAsync(string key, Func> factory, TimeSpan? expiry = null, CancellationToken ct = default); } public class RedisCacheService : ICacheService { private readonly IDistributedCache _cache; private readonly ILogger _logger; private static readonly TimeSpan DefaultExpiry = TimeSpan.FromMinutes(30); public RedisCacheService( IDistributedCache cache, ILogger logger) { _cache = cache; _logger = logger; } public async Task GetAsync(string key, CancellationToken ct = default) { var data = await _cache.GetStringAsync(key, ct); if (data == null) { _logger.LogDebug("Cache miss for key: {Key}", key); return default; } _logger.LogDebug("Cache hit for key: {Key}", key); return JsonSerializer.Deserialize(data); } public async Task SetAsync( string key, T value, TimeSpan? expiry = null, CancellationToken ct = default) { var options = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = expiry ?? DefaultExpiry }; var data = JsonSerializer.Serialize(value); await _cache.SetStringAsync(key, data, options, ct); _logger.LogDebug("Cached key: {Key} with expiry: {Expiry}", key, expiry ?? DefaultExpiry); } public async Task RemoveAsync(string key, CancellationToken ct = default) { await _cache.RemoveAsync(key, ct); _logger.LogDebug("Removed cache key: {Key}", key); } public async Task GetOrSetAsync( string key, Func> factory, TimeSpan? expiry = null, CancellationToken ct = default) { var cached = await GetAsync(key, ct); if (cached != null) return cached; var value = await factory(); await SetAsync(key, value, expiry, ct); return value; } } ``` ### Cache-Aside Pattern in Query Handler ```csharp /// /// EN: Query handler with cache-aside pattern. /// VI: Query handler với cache-aside pattern. /// public class GetUserProfileQueryHandler : IRequestHandler { private readonly ICacheService _cache; private readonly IUserRepository _userRepository; private static readonly TimeSpan CacheExpiry = TimeSpan.FromMinutes(15); public async Task Handle( GetUserProfileQuery request, CancellationToken ct) { var cacheKey = $"user:profile:{request.UserId}"; // EN: Try get from cache first // VI: Thử lấy từ cache trước return await _cache.GetOrSetAsync( cacheKey, async () => { var user = await _userRepository.GetByIdAsync(request.UserId, ct); return user?.ToProfileDto(); }, CacheExpiry, ct); } } ``` ### Cache Invalidation on Write ```csharp /// /// EN: Invalidate cache when data changes. /// VI: Xóa cache khi dữ liệu thay đổi. /// public class UpdateUserProfileCommandHandler : IRequestHandler { private readonly ICacheService _cache; private readonly IUserRepository _userRepository; public async Task Handle( UpdateUserProfileCommand request, CancellationToken ct) { var user = await _userRepository.GetByIdAsync(request.UserId, ct) ?? throw new NotFoundException("User", request.UserId); user.UpdateProfile(request.DisplayName, request.Bio); await _userRepository.UnitOfWork.SaveChangesAsync(ct); // EN: Invalidate cache after update // VI: Xóa cache sau khi cập nhật await _cache.RemoveAsync($"user:profile:{request.UserId}", ct); return Unit.Value; } } ``` ### Shopping Cart with Redis Hash ```csharp /// /// EN: Shopping cart repository using Redis Hash. /// VI: Repository giỏ hàng dùng Redis Hash. /// public class RedisCartRepository : ICartRepository { private readonly IDatabase _redis; private static readonly TimeSpan CartExpiry = TimeSpan.FromDays(7); public RedisCartRepository(IConnectionMultiplexer redis) { _redis = redis.GetDatabase(); } public async Task GetAsync(string cartId) { var data = await _redis.StringGetAsync($"cart:{cartId}"); return data.IsNullOrEmpty ? null : JsonSerializer.Deserialize(data!); } public async Task SaveAsync(Cart cart) { var key = $"cart:{cart.Id}"; var data = JsonSerializer.Serialize(cart); await _redis.StringSetAsync(key, data, CartExpiry); } public async Task DeleteAsync(string cartId) { await _redis.KeyDeleteAsync($"cart:{cartId}"); } } ``` ## Common Mistakes / Lỗi Thường Gặp ### 1. No Cache Expiry ```csharp // ❌ BAD: No expiry leads to stale data await _cache.SetStringAsync(key, data); // ✅ GOOD: Always set expiry await _cache.SetStringAsync(key, data, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) }); ``` ### 2. Cache Stampede ```csharp // ❌ BAD: Multiple requests hit DB simultaneously on cache miss var data = await _cache.GetAsync(key); if (data == null) { data = await _db.GetDataAsync(); await _cache.SetAsync(key, data); } // ✅ GOOD: Use locking to prevent stampede var data = await _cache.GetOrSetWithLockAsync(key, async () => { return await _db.GetDataAsync(); }); ``` ### 3. Caching Null Values ```csharp // ❌ BAD: Not caching null causes repeated DB calls if (data != null) await _cache.SetAsync(key, data); // ✅ GOOD: Cache null with short TTL var cacheValue = new CacheWrapper { Value = data, IsNull = data == null }; await _cache.SetAsync(key, cacheValue, data == null ? TimeSpan.FromMinutes(1) : TimeSpan.FromHours(1)); ``` ## Quick Reference / Tham Chiếu Nhanh ### Cache Key Naming ```csharp // EN: Pattern: {entity}:{id}:{optional-subkey} // VI: Pattern: {entity}:{id}:{optional-subkey} "user:profile:123" "order:items:456" "product:details:789" "user:orders:123:page:1" ``` ### Common TTL Values | Data Type | TTL | Reason | |-----------|-----|--------| | User profile | 15-30 min | Changes moderately | | Product catalog | 1-4 hours | Batch updates | | Shopping cart | 7 days | User convenience | | Session | 30 min sliding | Security | | Rate limit | 1 min | Short window | ### Redis Commands via CLI ```bash # EN: View all keys with pattern redis-cli KEYS "user:*" # EN: Get TTL of key redis-cli TTL "user:profile:123" # EN: Delete keys by pattern redis-cli KEYS "cart:*" | xargs redis-cli DEL ``` ## Resources / Tài Nguyên - [Detailed Examples](./references/REFERENCE.md) - Full code examples - [Error Handling](../error-handling-patterns/SKILL.md) - Cache failure handling - [Repository Pattern](../repository-pattern/SKILL.md) - Data access patterns - [Docker Traefik](../docker-traefik/SKILL.md) - Redis container setup