0

Series Mô hình Hot/Cold trong Redis #1: Nghệ thuật Phân lô Dữ liệu và Mẫu thiết kế Cache-Aside

Trong các dự án trước, chúng ta đã dùng Redis để làm Queue và đệm dữ liệu. Redis thì quá nổi tiếng với tốc độ phản hồi tính bằng mili-giây, nhưng nó có một tử huyệt chí mạng: Nó lưu mọi thứ trên RAM.

Trong hệ thống AFC Metro hay các đợt Flash Sale của Hasaki, dữ liệu đổ về là khổng lồ. Nếu bạn ném toàn bộ lịch sử quẹt thẻ hay toàn bộ danh mục sản phẩm vào Redis, Server sẽ bị "thổi bay" RAM ngay lập tức, hoặc công ty sẽ phá sản vì phải thuê máy chủ quá đắt tiền.

Đó là lúc chúng ta phải áp dụng Mô hình Hot/Cold Data. Đây là tư duy phân lô dữ liệu chuẩn mực của các hệ thống lớn. Hôm nay, chúng ta sẽ bước vào bài đầu tiên để định hình lại cách phân bổ tài nguyên bộ nhớ!

1. Triết lý Hot Data và Cold Data là gì?

Không phải dữ liệu nào cũng sinh ra bình đẳng. Bạn phải phân loại chúng theo "Nhiệt độ":

  • 🔥 Hot Data (Dữ liệu Nóng): Là những dữ liệu được truy cập liên tục, yêu cầu tốc độ phản hồi chớp nhoáng, và thường xuyên thay đổi trong thời gian ngắn.
  • Ví dụ trong Metro: Trạng thái ví điện tử của hành khách đang ở trong nhà ga (vừa Tap In), thông tin thẻ đang hoạt động trong ngày hôm nay.
  • Lưu trữ ở đâu: Bộ nhớ đệm Redis (RAM).
  • ❄️ Cold Data (Dữ liệu Lạnh): Là dữ liệu khổng lồ, rất ít khi bị truy cập lại, hoặc chỉ dùng để xuất báo cáo cuối tháng.
  • Ví dụ trong Metro: Lịch sử quẹt thẻ của hành khách từ 6 tháng trước, biên lai đồng bộ gửi cho Hitachi tháng trước.
  • Lưu trữ ở đâu: Cơ sở dữ liệu vật lý PostgreSQL (Ổ cứng/Disk).

Mục tiêu của Mô hình Hot/Cold: Giữ cho Redis luôn nhỏ gọn (chỉ chứa Hot Data) để duy trì tốc độ siêu tốc, đồng thời đảm bảo an toàn cho dữ liệu dài hạn dưới PostgreSQL.

2. Cache-Aside Pattern: Cầu nối giữa Nóng và Lạnh

Để luân chuyển dữ liệu giữa Redis và PostgreSQL, mẫu thiết kế kinh điển nhất là Cache-Aside (Tải lười biếng - Lazy Loading).

Luồng hoạt động khi cần lấy thông tin ví của hành khách:

  1. Hỏi Redis trước: Có thông tin thẻ card_uid này không?
  2. Cache Hit (Có trên Redis): Quá ngon! Lấy ngay về, không thèm gọi Database.
  3. Cache Miss (Không có trên Redis): Lúc này, hệ thống mới vòng xuống truy vấn PostgreSQL (Cold Data). Lấy được dữ liệu xong, tiện tay "đẩy" nó lên Redis để lần quẹt thẻ sau nó trở thành Hot Data.

3. Triển khai Cache-Aside bằng mã nguồn Laravel

Một Junior thường sẽ viết một đống lệnh if/else để check Redis rồi mới query Database. Nhưng ở chuẩn Senior, chúng ta sử dụng sức mạnh đóng gói của Laravel.

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use App\Models\Wallet;

class AFCWalletService
{
    public function getWalletBalance($cardUid)
    {
        // Định nghĩa Key trên Redis
        $cacheKey = "wallet_balance:{$cardUid}";

        // Áp dụng Cache-Aside bằng hàm remember()
        // Dữ liệu sẽ sống trên Redis (Hot Data) trong đúng 24 giờ.
        $balance = Cache::remember($cacheKey, 86400, function () use ($cardUid) {
            
            // Đoạn code trong Closure này CHỈ CHẠY khi Cache Miss (Không có trên Redis)
            // Lúc này chúng ta mới "chạm" vào Cold Data dưới PostgreSQL
            $wallet = Wallet::where('card_uid', $cardUid)->first();
            
            if (!$wallet) {
                throw new \Exception("Thẻ không hợp lệ!");
            }
            
            return $wallet->balance;
        });

        return $balance;
    }
}

4. Quy tắc Sinh tử: Thời gian sống (TTL)

Bạn có để ý con số 86400 (24 giờ) trong đoạn code trên không? Đó là Time-To-Live (TTL).

Đây là yếu tố quyết định sự sống còn của mô hình Hot/Cold. Nếu bạn không cài đặt TTL, mọi Cold Data lấy từ Database lên sẽ kẹt lại vĩnh viễn trên Redis, biến Redis thành một cái bãi rác và sớm muộn cũng gây tràn RAM (OOM - Out of Memory).

Bằng cách thiết lập TTL 24 giờ, hệ thống ngầm hiểu: "Nếu hành khách này quẹt thẻ hôm nay, dữ liệu của họ là Hot. Nhưng nếu 24 giờ tới họ không đi Metro nữa, hãy tự động xóa khỏi Redis để nhường chỗ cho hành khách khác. Dữ liệu của họ sẽ tự động 'nguội' đi và yên vị dưới PostgreSQL."

Tổng kết Bài 1

Chúng ta đã nắm được triết lý tối giản cốt lõi của Backend: Tiết kiệm RAM. Bằng cách phân định rạch ròi đâu là dữ liệu Nóng, đâu là dữ liệu Lạnh và kết nối chúng qua mô hình Cache-Aside có gài sẵn "kíp nổ thời gian" (TTL), hệ thống của bạn đã có thể phục vụ hàng triệu Request mà Redis vẫn cực kỳ nhẹ nhàng.

Trong môi trường thực tế, nếu một thẻ Metro bất ngờ hết tiền và được nạp thêm ngay lập tức tại quầy, dữ liệu dưới PostgreSQL đã thay đổi nhưng trên Redis (Hot Data) vẫn đang giữ số dư cũ. Ở bài 2, bạn muốn chúng ta giải quyết vấn đề Tính nhất quán dữ liệu (Data Consistency) với kỹ thuật Write-Through / Write-Behind, hay muốn tìm hiểu cách cấu hình Thuật toán LRU (Least Recently Used) để Redis tự động đá dữ liệu lạnh ra khỏi RAM khi bị đầy?


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí