14 KiB
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
- Nguyên Tắc Chung
- Quy Ước Đặt Tên Key (Naming Convention)
- Cấu Trúc Key Theo Namespace
- Bảng Mẫu Key Theo Chức Năng
- Quy Tắc Về Độ Dài Key
- Chọn Đúng Data Structure
- Quản Lý Key: SCAN thay vì KEYS
- Chiến Lược TTL & Expiration
- Cache Invalidation
- 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
KEYScommand 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!"