Redis Caching Thực Chiến: Từ Bài Toán Sập DB Đến Hệ Thống Gánh Tải Triệu Request

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Câu chuyện thực tế: Khi Database “thở không ra hơi”

Sáu tháng trước, hệ thống mình quản lý liên tục bị treo mỗi khi bộ phận Marketing chạy chiến dịch. Dashboard của AWS báo CPU của con RDS PostgreSQL luôn chạm ngưỡng 95%. Lúc đó, phương án nâng cấp cấu hình (Vertical Scaling) tốn thêm gần 400 USD/tháng nhưng không giải quyết được gốc rễ vấn đề. Sau khi phân tích, mình thấy 80% truy vấn là các câu lệnh SELECT lặp đi lặp lại. Đây là lúc Redis xuất hiện để làm “lá chắn” cho Database.

Nhiều bạn thường coi Redis là một cái kho chứa tạm đơn giản. Tuy nhiên, nếu thiếu chiến lược caching rõ ràng, bạn sẽ sớm gặp rắc rối với dữ liệu cũ (stale data) hoặc tình trạng Cache Stampede làm sập hệ thống ngay lúc cao điểm.

Triển khai nhanh với Docker: Đừng quên giới hạn tài nguyên

Mình luôn ưu tiên dùng Docker để đồng nhất môi trường từ máy cá nhân lên server. Sử dụng bản Alpine sẽ giúp image nhẹ và khởi động nhanh hơn.

# Chạy Redis với giới hạn 512MB RAM để bảo vệ server
docker run --name redis-itfromzero \
  -d -p 6379:6379 \
  redis:alpine \
  redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru

Lưu ý quan trọng: Tham số allkeys-lru là cứu cánh khi bộ nhớ đầy. Nó giúp Redis tự động xóa các key ít dùng nhất thay vì trả về lỗi OOM (Out Of Memory) khiến ứng dụng bị gián đoạn.

Chiến lược Cache-Aside: Đơn giản nhưng cần sự tỉ mỉ

Đây là mô hình phổ biến nhất. Luồng xử lý: App kiểm tra Redis, nếu có (Cache Hit) thì trả về ngay (thường chỉ mất 2-5ms). Nếu không (Cache Miss), app mới truy vấn DB và lưu lại vào Redis cho lần sau.

Ví dụ thực thi với Python

import redis
import json

r = redis.Redis(host='localhost', port=6379, db=0)

def get_product_detail(product_id):
    cache_key = f"product:{product_id}"
    # 1. Check cache
    cached_data = r.get(cache_key)
    if cached_data:
        return json.loads(cached_data)
    
    # 2. Query DB (giả lập mất 200ms)
    product_from_db = db.query(f"SELECT * FROM products WHERE id={product_id}")
    
    # 3. Set cache kèm TTL 1 giờ
    r.setex(cache_key, 3600, json.dumps(product_from_db))
    return product_from_db

Kinh nghiệm đau thương của mình: Đừng bao giờ quên xóa cache khi dữ liệu DB thay đổi. Một lần mình quên lệnh r.delete(cache_key) khi cập nhật giá, kết quả là khách hàng thấy giá khuyến mãi nhưng lúc thanh toán lại ra giá cũ.

Write-Through: Khi dữ liệu cần sự nhất quán tuyệt đối

Ngược với Cache-Aside, Write-Through ghi dữ liệu vào Redis đồng thời với DB. Cách này đảm bảo cache luôn mới nhất, loại bỏ hoàn toàn rủi ro dữ liệu cũ.

Mình thường áp dụng nó cho các thông tin nhạy cảm như số dư ví hoặc session người dùng. Dù latency khi ghi tăng nhẹ do phải chờ cả hai bên xác nhận, nhưng đổi lại bạn có sự an tâm về tính chính xác của dữ liệu.

Nghệ thuật quản lý TTL và kỹ thuật Jitter

Đặt TTL (Time To Live) là một bài toán cân não. Sau thời gian vận hành production, mình chia làm 3 nhóm chính:

  • Dữ liệu ít biến động (Danh mục, cấu hình): TTL từ 24h đến 48h.
  • Nội dung thường xuyên cập nhật (Sản phẩm, tin tức): TTL 1h đến 6h.
  • Dữ liệu nóng (Số lượng tồn kho): TTL cực ngắn (30s – 2p) hoặc không cache.

Để tránh Cache Avalanche (hiện tượng hàng loạt key hết hạn cùng lúc khiến DB sập), hãy dùng kỹ thuật Jitter. Thay vì set cứng 3600s, hãy dùng 3600 + random(0, 300) để phân tán thời điểm hết hạn.

Giám sát hiệu quả: Đừng để Cache chạy mù

Làm sao biết chiến lược của bạn đúng? Hãy dùng lệnh redis-cli info stats để soi chỉ số keyspace_hitskeyspace_misses.

Tỷ lệ Cache Hit Ratio nên đạt trên 80%. Nếu con số này quá thấp, bạn đang lãng phí tài nguyên Redis cho những dữ liệu ít được truy cập. Ngoài ra, hãy cẩn thận với lệnh monitor trên production. Nó có thể làm giảm 30-50% hiệu năng của Redis server chỉ để log dữ liệu.

Kết quả sau khi tối ưu? CPU RDS của mình giảm từ 95% xuống ổn định ở mức 15-20%. Quan trọng hơn, trải nghiệm người dùng mượt mà hơn hẳn khi các trang quan trọng load chỉ mất chưa đầy 100ms.

Share: