# Hướng Dẫn Đặt Tên Redis Key Trong Dự Án > **Tham khảo:** [Redis Namespace and Other Keys to Developing with Redis](https://redis.io/blog/5-key-takeaways-for-developing-with-redis/) --- ## Mục Lục 1. [Nguyên Tắc Chung](#1-nguyên-tắc-chung) 2. [Quy Ước Đặt Tên Key (Naming Convention)](#2-quy-ước-đặt-tên-key-naming-convention) 3. [Cấu Trúc Key Theo Namespace](#3-cấu-trúc-key-theo-namespace) 4. [Bảng Mẫu Key Theo Chức Năng](#4-bảng-mẫu-key-theo-chức-năng) 5. [Quy Tắc Về Độ Dài Key](#5-quy-tắc-về-độ-dài-key) 6. [Chọn Đúng Data Structure](#6-chọn-đúng-data-structure) 7. [Quản Lý Key: SCAN thay vì KEYS](#7-quản-lý-key-scan-thay-vì-keys) 8. [Chiến Lược TTL & Expiration](#8-chiến-lược-ttl--expiration) 9. [Cache Invalidation](#9-cache-invalidation) 10. [Ví Dụ Thực Tế Trong Dự Án](#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 ```bash # 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): ```csharp // Đú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! ```csharp // 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#: ```csharp 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# ```csharp /// /// Đị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. /// 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!"*