“Thêm Redis vào là performance sẽ tăng thôi.”
Câu này anh em dev nào cũng hay nói. Nhưng ít người hiểu tại sao nó lại hiệu quả — hay khi nào nó thất bại thảm hại.
Cache là tối ưu hiệu năng mạnh nhất bạn có thể áp dụng. Đồng thời cũng là cách dễ nhất để serve dữ liệu cũ và để user nhìn thấy thông tin sai trong nhiều giờ.
Đây là Hướng dẫn chuyên sâu về Caching. Chúng ta sẽ đi qua Redis data types, 3 chiến lược eviction, và vấn đề khó nhất Khoa học Máy tính, và các pattern phân biệt senior với junior.
Phần 1: Nền tảng (Mô hình tư duy)
Tờ giấy nhớ vs. Tủ hồ sơ ở tầng hầm
Mỗi khi app cần dữ liệu, nó phải chọn:
Database = Tủ hồ sơ (ở tầng hầm) Chính xác. Có tất cả mọi thứ. Nhưng bạn phải đi bộ xuống tầng hầm, mở ngăn, tìm hồ sơ, rồi leo lên lại. Chậm: 5–100ms mỗi lần.
Cache (Redis) = Tờ giấy nhớ dán trên màn hình Bạn đã ghi câu trả lời ở đây trước rồi. Cứ nhìn vào là xong. Nhanh: 0.1–1ms.
Mục tiêu: trả lời từ Tờ giấy nhớ bất cứ khi nào có thể. Chỉ xuống Tầng hầm khi tờ giấy không có hoặc thông tin đã lỗi thời.
Hai câu hỏi cốt lõi
Mọi quyết định về caching đều xoay quanh hai câu hỏi:
- Dữ liệu “tươi” được bao lâu? (TTL — Time To Live)
- Khi nào cache đầy thì làm gì? (Eviction Strategy)
Phần 2: Điều tra (Redis Data Types)
Redis không chỉ là key-value store đơn giản. Nó có 5 cấu trúc dữ liệu chính, mỗi cái giải quyết một vấn đề khác nhau.
| Kiểu | Vật thể tương ứng | Use Case |
|---|---|---|
| String | Tờ giấy nhớ | Session token, cache đơn giản, bộ đếm. |
| Hash | Bảng tính mini | User profile (user:123 → {name, email, tuổi}). |
| List | Hàng đợi (FIFO) | Bản tin hoạt động (50 hành động gần nhất). Task queue. |
| Set | Hộp bi không trùng | Khách truy cập duy nhất hôm nay. Tags của bài viết. |
| Sorted Set | Bảng xếp hạng | Top 10 user theo điểm. Cửa sổ rate limiting. |
TTL (Time To Live)
Mọi cache entry phải có TTL — thời gian hết hạn. Không có TTL là memory leak.
| |
Phần 3: Chẩn đoán (Cache Invalidation — Vấn đề khó nhất)
Phil Karlton có câu nói nổi tiếng:
“Chỉ có hai thứ khó trong Khoa học Máy tính: cache invalidation và đặt tên.”
3 Chiến lược Eviction (Khi Cache đầy)
Khi Redis hết RAM, nó phải xóa thứ gì đó. Xóa cái nào?
| Chiến lược | Cái bị xóa | Dùng khi |
|---|---|---|
| LRU (Ít được dùng nhất gần đây) | Key lâu không được truy cập nhất. | Tổng quát. Mặc định an toàn. |
| LFU (Ít được dùng nhất tổng cộng) | Key được truy cập ít lần nhất. | Khi tần suất truy cập quan trọng. |
| Random (allkeys-random) | Một key ngẫu nhiên. | Hầu như không bao giờ dùng. Nguy hiểm. |
Cấu hình eviction policy:
| |
3 Dạng thất bại của Cache
1. Cache Miss (Bình thường) Key không có trong cache → lấy từ DB → lưu vào cache → trả về. Đây là bình thường.
2. Cache Stampede (Nguy hiểm) TTL hết hạn. 10,000 user cùng lúc request cùng một dữ liệu. Tất cả đều miss. Tất cả đều đánh vào DB cùng lúc. DB sập.
Cách sửa: Dùng lock (Redis SETNX) khi regenerate cache entry. Chỉ một process regenerate; các process còn lại chờ.
| |
3. Cache Poisoning (Thảm họa) Bạn cache dữ liệu sai. Giờ mọi user đều thấy thông tin sai cho đến khi TTL hết. Không có cách sửa dễ — phải xóa key thủ công.
| |
Phần 4: Giải pháp (Sách nấu ăn Python)
1. Cache-Aside Pattern (Chuẩn mực)
App tự điều khiển cache. Pattern phổ biến nhất.
| |
2. Invalidate on Write (Giữ cache trung thực)
Khi dữ liệu thay đổi, xóa cache ngay lập tức.
| |
3. Rate Limiting (Redis làm bộ đếm)
Giới hạn API: 100 request mỗi user mỗi phút.
| |
Mô hình tư duy chốt hạ
| |
Quy tắc vàng:
- Đặt TTL cho mọi thứ. Không TTL = Memory leak.
- Xóa cache khi ghi, không chỉ chờ TTL hết hạn.
- Cache đúng tầng: Tránh cache dữ liệu một nửa mà khó biết khi nào vô hiệu hóa.
