---
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
/// <summary>
/// EN: Configure Redis distributed cache.
/// VI: Cấu hình Redis distributed cache.
/// </summary>

// 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<IConnectionMultiplexer>(sp =>
    ConnectionMultiplexer.Connect(builder.Configuration["Redis:ConnectionString"]!));
```

### Cache Service Implementation

```csharp
/// <summary>
/// EN: Generic cache service with typed operations.
/// VI: Cache service generic với typed operations.
/// </summary>
public interface ICacheService
{
    Task<T?> GetAsync<T>(string key, CancellationToken ct = default);
    Task SetAsync<T>(string key, T value, TimeSpan? expiry = null, CancellationToken ct = default);
    Task RemoveAsync(string key, CancellationToken ct = default);
    Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiry = null, CancellationToken ct = default);
}

public class RedisCacheService : ICacheService
{
    private readonly IDistributedCache _cache;
    private readonly ILogger<RedisCacheService> _logger;
    private static readonly TimeSpan DefaultExpiry = TimeSpan.FromMinutes(30);

    public RedisCacheService(
        IDistributedCache cache,
        ILogger<RedisCacheService> logger)
    {
        _cache = cache;
        _logger = logger;
    }

    public async Task<T?> GetAsync<T>(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<T>(data);
    }

    public async Task SetAsync<T>(
        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<T> GetOrSetAsync<T>(
        string key,
        Func<Task<T>> factory,
        TimeSpan? expiry = null,
        CancellationToken ct = default)
    {
        var cached = await GetAsync<T>(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
/// <summary>
/// EN: Query handler with cache-aside pattern.
/// VI: Query handler với cache-aside pattern.
/// </summary>
public class GetUserProfileQueryHandler 
    : IRequestHandler<GetUserProfileQuery, UserProfileDto?>
{
    private readonly ICacheService _cache;
    private readonly IUserRepository _userRepository;
    private static readonly TimeSpan CacheExpiry = TimeSpan.FromMinutes(15);

    public async Task<UserProfileDto?> 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
/// <summary>
/// EN: Invalidate cache when data changes.
/// VI: Xóa cache khi dữ liệu thay đổi.
/// </summary>
public class UpdateUserProfileCommandHandler 
    : IRequestHandler<UpdateUserProfileCommand, Unit>
{
    private readonly ICacheService _cache;
    private readonly IUserRepository _userRepository;

    public async Task<Unit> 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
/// <summary>
/// EN: Shopping cart repository using Redis Hash.
/// VI: Repository giỏ hàng dùng Redis Hash.
/// </summary>
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<Cart?> GetAsync(string cartId)
    {
        var data = await _redis.StringGetAsync($"cart:{cartId}");
        return data.IsNullOrEmpty 
            ? null 
            : JsonSerializer.Deserialize<Cart>(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<Data>(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<Data> { 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
