Featured image of post Caching & Redis: Mô hình tư duy 'Tờ giấy nhớ'

Caching & Redis: Mô hình tư duy 'Tờ giấy nhớ'

Tại sao Redis làm mọi thứ nhanh hơn? Hướng dẫn chuyên sâu về cache invalidation (vấn đề khó nhất CS), các chiến lược eviction, và Redis data types.

“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:

  1. Dữ liệu “tươi” được bao lâu? (TTL — Time To Live)
  2. 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ểuVật thể tương ứngUse Case
StringTờ giấy nhớSession token, cache đơn giản, bộ đếm.
HashBảng tính miniUser profile (user:123 → {name, email, tuổi}).
ListHàng đợi (FIFO)Bản tin hoạt động (50 hành động gần nhất). Task queue.
SetHộp bi không trùngKhách truy cập duy nhất hôm nay. Tags của bài viết.
Sorted SetBảng xếp hạngTop 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.

1
2
3
4
5
6
7
8
9
import redis

r = redis.Redis()

# Set với TTL 5 phút
r.set("user:123:profile", json.dumps(user_data), ex=300)

# Kiểm tra thời gian còn lại
r.ttl("user:123:profile")  # Trả về giây còn lại, -1 nếu không TTL, -2 nếu key không tồn tại

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ượcCái bị xóaDù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:

1
maxmemory-policy allkeys-lru

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ờ.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import redis, time

r = redis.Redis()

def get_heavy_data(key: str):
    cached = r.get(key)
    if cached:
        return json.loads(cached)

    # Chỉ MỘT process được regenerate
    lock_key = f"lock:{key}"
    if r.set(lock_key, "1", nx=True, ex=10):  # nx=SETNX (chỉ set nếu chưa tồn tại)
        data = expensive_db_query()
        r.set(key, json.dumps(data), ex=300)
        r.delete(lock_key)
        return data
    else:
        time.sleep(0.1)
        return get_heavy_data(key)  # Thử lại

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.

1
2
3
4
5
# Xóa một key cụ thể
redis-cli DEL "user:123:profile"

# Xóa tất cả key theo pattern (cẩn thận khi dùng production!)
redis-cli --scan --pattern "user:*" | xargs redis-cli DEL

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def get_user(user_id: int) -> dict:
    cache_key = f"user:{user_id}"
    
    # 1. Thử cache trước
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)  # Cache HIT
    
    # 2. Cache miss: truy vấn DB
    user = User.objects.get(id=user_id)
    data = serialize(user)
    
    # 3. Lưu vào cache cho lần sau (TTL: 5 phút)
    redis_client.set(cache_key, json.dumps(data), ex=300)
    
    return data  # Cache MISS

2. Invalidate on Write (Giữ cache trung thực)

Khi dữ liệu thay đổi, xóa cache ngay lập tức.

1
2
3
4
5
6
def update_user(user_id: int, new_data: dict):
    # 1. Cập nhật DB
    User.objects.filter(id=user_id).update(**new_data)
    
    # 2. XÓA NGAY cache (đừng chờ TTL!)
    redis_client.delete(f"user:{user_id}")

3. Rate Limiting (Redis làm bộ đếm)

Giới hạn API: 100 request mỗi user mỗi phút.

1
2
3
4
5
6
7
8
def is_rate_limited(user_id: int, limit: int = 100) -> bool:
    key = f"rate:{user_id}:{int(time.time() // 60)}"  # Key đổi mỗi phút
    
    count = redis_client.incr(key)  # Tăng nguyên tử (Atomic increment)
    if count == 1:
        redis_client.expire(key, 60)  # Đặt TTL ngay lần đầu tăng
    
    return count > limit

Mô hình tư duy chốt hạ

1
2
3
4
5
6
Cache (Redis) -> Tờ giấy nhớ. Nhanh, tạm thời, có thể lỗi thời.
Database      -> Tủ hồ sơ tầng hầm. Chậm, chính xác, vĩnh viễn.

LRU Eviction  -> "Xóa thứ lâu ngày không ai ngó đến."
Cache Stampede -> 10,000 người lật tờ giấy nhớ cùng lúc.
Cache Invalidation -> Vấn đề khó nhất: biết KHI NÀO vứt tờ giấy nhớ đi.

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.
Được tạo với sự lười biếng tình yêu 🦥

Subscribe to My Newsletter