Files
CleanArchitecture-template/base/.agent/rules/Redis.md

14 KiB
Raw Blame History

trigger
trigger
always_on

Hướng Dẫn Đặt Tên Redis Key Trong Dự Án

Tham khảo: Redis Namespace and Other Keys to Developing with Redis


Mục Lục

  1. Nguyên Tắc Chung
  2. Quy Ước Đặt Tên Key (Naming Convention)
  3. Cấu Trúc Key Theo Namespace
  4. Bảng Mẫu Key Theo Chức Năng
  5. Quy Tắc Về Độ Dài Key
  6. Chọn Đúng Data Structure
  7. Quản Lý Key: SCAN thay vì KEYS
  8. Chiến Lược TTL & Expiration
  9. Cache Invalidation
  10. Ví Dụ Thực Tế Trong Dự Án

1. Nguyên Tắc Chung

Theo bài viết từ Redis, có 5 điểm quan trọng khi phát triển với Redis:

# Nguyên tắc Mô tả
1 Namespace cho key Sử dụng dấu : để phân tách các phần của tên key, giúp dễ quản lý và tìm kiếm
2 Giữ key ngắn gọn Key name cũng chiếm bộ nhớ — key dài 12 ký tự tốn thêm ~15% RAM so với key 6 ký tự (trên 1 triệu key)
3 Dùng đúng data structure Hash, List, Set, Sorted Set — mỗi loại phù hợp với một use case khác nhau
4 Dùng SCAN, không dùng KEYS Lệnh KEYS có thể block server, SCAN an toàn hơn cho production
5 Sử dụng Lua Scripts Xử lý logic phía server để giảm latency và tối ưu hiệu suất

2. Quy Ước Đặt Tên Key (Naming Convention)

Format chung

{project}:{service}:{entity}:{identifier}
  • Dấu phân cách: Luôn dùng dấu hai chấm : (colon) — đây là convention chuẩn của Redis
  • Chữ thường: Tất cả các phần của key đều viết lowercase
  • Không dấu cách, không ký tự đặc biệt: Chỉ dùng chữ cái, số, dấu : và dấu - hoặc _

Đúng

myapp:user:profile:12345
myapp:order:detail:ORD-001
myapp:cache:product:list:page:1

Sai

MyApp_User_Profile_12345      # Dùng underscore thay vì colon, viết hoa
user profile 12345             # Có dấu cách
MYAPP:USER:PROFILE:12345       # Viết hoa toàn bộ (lãng phí bộ nhớ)

3. Cấu Trúc Key Theo Namespace

Áp dụng cho dự án Clean Architecture, cấu trúc key nên phản ánh rõ layer và module:

{app}:{layer}:{entity}:{action/scope}:{identifier}

Các prefix theo layer

Prefix Ý nghĩa Ví dụ
app:cache Cache dữ liệu app:cache:product:list
app:session Quản lý session app:session:user:abc123
app:rate Rate limiting app:rate:api:login:192.168.1.1
app:lock Distributed lock app:lock:order:process:ORD-001
app:queue Message queue app:queue:email:pending
app:temp Dữ liệu tạm thời app:temp:otp:user:12345
app:pub Pub/Sub channels app:pub:notifications:user:12345
app:counter Bộ đếm app:counter:visit:page:home

4. Bảng Mẫu Key Theo Chức Năng

Authentication & Authorization

Key Pattern Data Type TTL Mô tả
app:session:{sessionId} Hash 30 phút Thông tin session người dùng
app:token:refresh:{userId} String 7 ngày Refresh token
app:token:blacklist:{jti} String Thời gian còn lại của token JWT bị thu hồi
app:temp:otp:{userId} String 5 phút Mã OTP xác thực
app:rate:login:{ip} String (counter) 15 phút Giới hạn số lần đăng nhập

Cache Dữ Liệu (CRUD)

Key Pattern Data Type TTL Mô tả
app:cache:{entity}:detail:{id} Hash/String 10 phút Cache chi tiết 1 entity
app:cache:{entity}:list:{hash} String (JSON) 5 phút Cache danh sách có phân trang/filter
app:cache:{entity}:count String 5 phút Cache tổng số record
app:cache:{entity}:ids:all Set 10 phút Tập hợp tất cả ID của entity

{hash} là hash MD5/SHA256 của query parameters (page, filter, sort) để tạo key unique cho mỗi truy vấn khác nhau.

🔔 Real-time & Pub/Sub

Key Pattern Data Type TTL Mô tả
app:pub:notification:{userId} Channel Kênh thông báo realtime
app:queue:email:pending List Hàng đợi gửi email
app:counter:online:users String Đếm user đang online

🔒 Distributed Locking

Key Pattern Data Type TTL Mô tả
app:lock:{resource}:{id} String 30 giây Lock tài nguyên để tránh race condition

5. Quy Tắc Về Độ Dài Key

Theo bài viết từ Redis:

"Storing 1,000,000 keys, each set with a 32-character value, will consume about 96MB when using 6-character key names, and 111MB with 12-character names. This overhead of more than 15% becomes quite significant as your number of keys grows."

Hướng dẫn cân bằng

Quy tắc Chi tiết
Tối đa 50 ký tự Giữ tổng chiều dài key không quá 50 ký tự
Viết tắt hợp lý usr thay vì user, prod thay vì product — nhưng phải có bảng chú giải
Không lạm dụng viết tắt Key phải đọc được, tránh như a:b:c:d:1
Ưu tiên rõ ràng nếu < 10K keys Nếu dataset nhỏ, ưu tiên key dễ đọc hơn key ngắn

Bảng viết tắt chuẩn (nếu cần tối ưu)

Viết tắt Đầy đủ
usr user
prod product
ord order
sess session
notif notification
cfg config
inv inventory
txn transaction

6. Chọn Đúng Data Structure

Theo bài viết, việc chọn đúng cấu trúc dữ liệu giúp tối ưu bộ nhớ và hiệu suất:

Data Structure Khi nào dùng Ví dụ trong dự án
String Giá trị đơn giản, counter, cache JSON app:cache:product:detail:123 → JSON string
Hash Object có nhiều field, profile user app:session:abc123{userId, role, name, exp}
List Queue, danh sách có thứ tự, cho phép trùng app:queue:email:pending → FIFO queue
Set Tập hợp unique, kiểm tra membership app:cache:user:ids:all → tập hợp user IDs
Sorted Set Leaderboard, ranking, timeline app:rank:score:board:game1 → ranking theo điểm
Bitmap Track true/false cho lượng lớn, analytics app:analytics:daily:login:2026-02-23 → bit per user

💡 Tips quan trọng:

  • Hash thay vì nhiều String: Nhóm dữ liệu liên quan vào 1 Hash thay vì tạo nhiều key String riêng lẻ → tiết kiệm bộ nhớ đáng kể
  • List thay vì Set: Nếu không cần kiểm tra uniqueness, List nhanh hơn và tốn ít RAM hơn
  • Tránh Sorted Set nếu không cần ranking: Sorted Set tốn nhiều bộ nhớ và phức tạp nhất

7. Quản Lý Key: SCAN thay vì KEYS

⚠️ KHÔNG BAO GIỜ dùng KEYS trong production!

Lệnh KEYS * sẽ:

  • Block toàn bộ Redis server cho đến khi hoàn thành
  • Tiêu tốn RAM nguy hiểm
  • Gây downtime nếu dataset lớn

Dùng SCAN để duyệt key an toàn

# Cú pháp
SCAN cursor [MATCH pattern] [COUNT count]

# Ví dụ: Tìm tất cả cache key của product
SCAN 0 MATCH "app:cache:product:*" COUNT 100

# Dùng HSCAN cho Hash
HSCAN app:session:abc123 0 MATCH "*"

# Dùng SSCAN cho Set
SSCAN app:cache:user:ids:all 0 COUNT 50

Trong code C# (StackExchange.Redis):

//   Đúng: Dùng SCAN
var server = redis.GetServer(endpoint);
var keys = server.Keys(pattern: "app:cache:product:*", pageSize: 100);

//  Sai: Dùng KEYS (block server)
// var keys = server.Keys(pattern: "app:cache:product:*", pageSize: int.MaxValue);

8. Chiến Lược TTL & Expiration

Loại dữ liệu TTL khuyến nghị Lý do
Cache API response 5 15 phút Đảm bảo data tương đối fresh
Session 30 phút 2 giờ Theo session timeout của app
OTP / Verification 3 10 phút Bảo mật
Refresh Token 7 30 ngày Theo chính sách auth
Rate Limit Counter 1 60 phút Theo window rate limit
Distributed Lock 10 60 giây Tránh deadlock
Analytics / Counter Không expire hoặc 24h Tùy yêu cầu business

Luôn đặt TTL cho mọi cache key!

// Luôn set expiration khi SET
await db.StringSetAsync("app:cache:product:detail:123", jsonData, TimeSpan.FromMinutes(10));

// Hoặc set TTL riêng
await db.KeyExpireAsync("app:cache:product:detail:123", TimeSpan.FromMinutes(10));

⚠️ Cảnh báo: Key không có TTL sẽ tồn tại mãi mãi → nguy cơ memory leak!


9. Cache Invalidation

Khi dữ liệu thay đổi trong database chính (SQL, MongoDB...), cần xóa cache Redis tương ứng:

Pattern: Tag-based Invalidation

# Khi tạo cache, thêm key vào một Set quản lý
SADD app:tags:product  app:cache:product:detail:123
SADD app:tags:product  app:cache:product:list:abc
SADD app:tags:product  app:cache:product:count

# Khi invalidate, duyệt Set và xóa tất cả
SMEMBERS app:tags:product  → lấy tất cả key liên quan
DEL app:cache:product:detail:123 app:cache:product:list:abc ...
DEL app:tags:product

Trong code C#:

public async Task InvalidateCacheByTagAsync(string tag)
{
    var db = _redis.GetDatabase();
    var tagKey = $"app:tags:{tag}";
    
    // Lấy tất cả cache key thuộc tag này
    var members = await db.SetMembersAsync(tagKey);
    
    if (members.Length > 0)
    {
        // Xóa tất cả cache key
        var keys = members.Select(m => (RedisKey)m.ToString()).ToArray();
        await db.KeyDeleteAsync(keys);
    }
    
    // Xóa luôn tag set
    await db.KeyDeleteAsync(tagKey);
}

10. Ví Dụ Thực Tế Trong Dự Án

Áp dụng quy ước cho dự án MyNewProjectName (Clean Architecture):

Entity: SampleEntity

# Cache chi tiết
app:cache:sample:detail:{id}

# Cache danh sách (hash = MD5 của query params)
app:cache:sample:list:{queryHash}

# Cache count
app:cache:sample:count

# Tag để invalidation
app:tags:sample → Set chứa tất cả key cache liên quan

# Lock khi cập nhật
app:lock:sample:update:{id}

Authentication Flow

# Session sau khi login
app:session:{sessionId}  → Hash { userId, role, loginAt, ip }

# Refresh token
app:token:refresh:{userId}  → "eyJhbGciOi..."

# OTP xác thực email
app:temp:otp:{userId}  → "123456"  (TTL: 5 phút)

# Blacklist JWT đã revoke
app:token:blacklist:{jti}  → "1"  (TTL: thời gian còn lại của token)

# Rate limit login
app:rate:login:{ip}  → counter  (TTL: 15 phút, max: 5 lần)

Constant class trong C#

/// <summary>
/// Định nghĩa tất cả Redis key patterns sử dụng trong dự án.
/// Sử dụng dấu ':' làm namespace separator theo convention chuẩn Redis.
/// </summary>
public static class RedisKeyPatterns
{
    private const string Prefix = "app";

    // ── Cache ──────────────────────────────────
    public static string CacheDetail(string entity, string id) 
        => $"{Prefix}:cache:{entity}:detail:{id}";
    
    public static string CacheList(string entity, string queryHash) 
        => $"{Prefix}:cache:{entity}:list:{queryHash}";
    
    public static string CacheCount(string entity) 
        => $"{Prefix}:cache:{entity}:count";

    // ── Tags (cho cache invalidation) ──────────
    public static string Tag(string entity) 
        => $"{Prefix}:tags:{entity}";

    // ── Session ────────────────────────────────
    public static string Session(string sessionId) 
        => $"{Prefix}:session:{sessionId}";

    // ── Token ──────────────────────────────────
    public static string RefreshToken(string userId) 
        => $"{Prefix}:token:refresh:{userId}";
    
    public static string BlacklistToken(string jti) 
        => $"{Prefix}:token:blacklist:{jti}";

    // ── Temporary ──────────────────────────────
    public static string Otp(string userId) 
        => $"{Prefix}:temp:otp:{userId}";

    // ── Rate Limiting ──────────────────────────
    public static string RateLimit(string action, string identifier) 
        => $"{Prefix}:rate:{action}:{identifier}";

    // ── Distributed Lock ───────────────────────
    public static string Lock(string resource, string id) 
        => $"{Prefix}:lock:{resource}:{id}";
}

📋 Checklist Trước Khi Tạo Key Mới

  • Key có sử dụng namespace với dấu : không?
  • Key có phản ánh đúng layer/module không?
  • Key có ngắn gọn nhưng vẫn dễ hiểu không? (< 50 ký tự)
  • Đã chọn đúng data structure (String/Hash/List/Set/Sorted Set)?
  • Đã đặt TTL phù hợp cho key?
  • Đã có chiến lược invalidation khi data thay đổi?
  • Đã thêm key pattern vào class RedisKeyPatterns?
  • Không dùng KEYS command trong code production?

💡 Ghi nhớ: "Luôn namespace key bằng dấu :, giữ key ngắn gọn, chọn đúng data structure, dùng SCAN thay vì KEYS, và luôn đặt TTL!"