0

Cache Key là gì? Tuyệt chiêu đặt tên giúp tôi thoát khỏi những đêm trắng "xóa Cache thủ công".

Chào anh em đồng đạo! Lại là mình với những câu chuyện "ăn hành" sau 5 năm nằm gai nếm mật cùng những dòng code.

Hôm nay, mình muốn lôi ra ánh sáng một khái niệm tưởng chừng như là chuyện "biết rồi, khổ lắm, nói mãi", nhưng lại là nguyên nhân của những con bug ngớ ngẩn và đáng sợ nhất hệ thống: Cache Key.

Nếu bạn nghĩ làm Cache chỉ đơn giản là gọi hàm set(tên_biến, dữ_liệu) và get(tên_biến), thì xin chúc mừng, bạn đang đứng trên bờ vực của một thảm họa y hệt mình ngày xưa. Hãy nghe mình kể câu chuyện này.

1. Mở bài: Sự cố "Râu ông nọ cắm cằm bà kia"

Đó là một buổi sáng thứ Hai giông bão. Dự án e-commerce của team vừa release tính năng "Dashboard Thống kê Đơn hàng" vào cuối tuần trước. Vì query lấy lịch sử đơn hàng khá nặng, mình đã quyết định dùng Redis để cache lại dữ liệu cho trang này, tự nhủ: "Trang load dưới 100ms, sếp chắc chắn sẽ tăng lương cho mình!".

Nhưng không, 9h sáng, kênh Slack của công ty nổ tung. Bộ phận CSKH báo về: "Anh ơi, tại sao chị user Nguyễn Thị A đăng nhập vào lại nhìn thấy lịch sử mua hàng, địa chỉ nhà và số điện thoại của anh Trần Văn B???".

Máu dồn lên não, mình bay vào check source code. Và đây là dòng code "ngu học" mà mình đã viết trong lúc ngái ngủ:

// Đoạn code suýt làm mình bay màu khỏi công ty
$orders = Cache::remember('user_dashboard_orders', 3600, function() {
    return Order::where('user_id', Auth::id())->get();
});

Anh em có nhìn ra vấn đề chưa? Mình đã dùng chung MỘT cái Cache Key (user_dashboard_orders) cho TẤT CẢ mọi người! Anh B đăng nhập vào lúc 8h sáng, hệ thống chạy query, lấy data của anh B và nhét vào cái hộp mang tên user_dashboard_orders. 8h05 sáng, chị A đăng nhập vào. Hệ thống thấy cái hộp user_dashboard_orders đã có sẵn dữ liệu, thế là nó hồn nhiên bê nguyên lịch sử mua hàng của anh B đập vào mặt chị A. Thảm họa rò rỉ dữ liệu (Data Leak) xuất phát từ một chuỗi string vô thưởng vô phạt!

2. Hành trình giác ngộ: Cache Key là gì và nghệ thuật đặt tên

Cú vấp đó tát cho mình tỉnh ngủ. Mình nhận ra: Cache Key không chỉ là một cái tên, nó là Tọa độ, là Danh tính của dữ liệu. Hãy tưởng tượng Cache (như Redis hay Memcached) là một khu tủ đồ siêu to khổng lồ ở siêu thị. Cache Key chính là cái mã số in trên chìa khóa của bạn. Nếu bạn thiết kế chìa khóa lởm, hai người sẽ mở chung một tủ (gây rò rỉ data), hoặc bạn sẽ không bao giờ tìm lại được món đồ mình đã cất (rác cache).

Sau hôm đó, mình bắt tay vào quy chuẩn hóa toàn bộ cách đặt Cache Key trong hệ thống. Mình đúc kết ra Pattern Đặt Tên (Convention) cực kỳ hiệu quả như sau:

Cấu trúc chuẩn: [app_name]:[entity]:[id]:[resource]:[version]

Thay vì đặt tên chung chung, mình chia Cache Key thành các "Namespace" (không gian tên) cách nhau bằng dấu hai chấm :. (Bật mí: Trên phần mềm quản lý Redis như Redis Desktop Manager, nó sẽ tự động gộp các key có dấu : thành dạng cây thư mục (folder) rất dễ nhìn).

Áp dụng vào đoạn code lỗi ngày xưa:

// Chân lý là đây!
$userId = Auth::id();
// Tạo key duy nhất cho mỗi user: vd "ecommerce:user:42:orders:v1"
$cacheKey = "ecommerce:user:{$userId}:orders:v1";

$orders = Cache::remember($cacheKey, 3600, function() use ($userId) {
    return Order::where('user_id', $userId)->get();
});

Tại sao cấu trúc này lại "thần thánh"?

  1. ecommerce: (App Name): Lỡ sau này công ty có 2-3 project chạy chung một con server Redis, data của các project sẽ không bị đấm nhau.
  2. user:42: (Entity & ID): Giải quyết triệt để bài toán "râu ông nọ cắm cằm bà kia". Data của user 42 chỉ nằm ở key của user 42. Khách nào dùng tủ đồ của khách đó.
  3. orders: (Resource): Xác định rõ loại dữ liệu đang cache (chỉ là đơn hàng, không lẫn với profile hay settings).
  4. v1: (Version): Đây là tuyệt chiêu Cache Busting. Giả sử bạn thay đổi cấu trúc bảng Orders, thêm một trường mới. Nếu dùng key cũ, user sẽ bị lỗi vì data trong cache thiếu trường đó. Thay vì phải hì hục chạy lệnh clear toàn bộ cache, bạn chỉ cần sửa code thành v2. Hệ thống tự động tạo ra những cái tủ đồ mới (v2), và tủ cũ (v1) sẽ tự bốc hơi khi hết hạn (TTL).

3. Bài học rút ra (The Takeaways)

  1. Tuyệt đối không dùng Static Key cho Dynamic Data: Nếu dữ liệu phụ thuộc vào User, vào URL, vào các tham số tìm kiếm (Filters)... thì Cache Key BẮT BUỘC phải chứa các yếu tố đó.
  2. Key dài một chút cũng không sao: Redis tra cứu theo thuật toán Hash cực kỳ nhanh. Việc bạn đặt tên key dài (ví dụ: shop:products:category_1:page_2:sort_price_asc) sẽ không làm hệ thống chậm đi, nhưng nó giúp bạn không bao giờ bị dính "Conflict Data".
  3. Tư duy dọn dẹp (Invalidation): Trước khi tạo một Cache Key, hãy tự hỏi: "Làm thế nào để mình xóa nó khi dữ liệu gốc trong Database bị thay đổi?". Việc đặt tên có cấu trúc sẽ giúp bạn xóa cache dễ dàng hơn (hoặc xài Cache Tags nếu framework hỗ trợ).

Lời kết

Việc thiết kế một hệ thống Cache hiệu quả không nằm ở việc bạn cài đặt Redis giỏi thế nào, mà nằm ở 80% tư duy thiết kế Cache Key và Cache Invalidation (xóa cache). Phil Karlton từng nói một câu bất hủ: "Có hai thứ khó nhất trong Khoa học máy tính: Xóa cache và Đặt tên". Và Cache Key thì "bao thầu" luôn cả hai cái khó đó!

Còn anh em thì sao? Trong các project hiện tại, team anh em có quy chuẩn (convention) nào chung để quản lý mớ bòng bong Cache Key không? Hay đã từng đập bàn phím vì không thể xóa nổi một cái cache do đặt tên vô tội vạ? Cùng comment tâm sự bên dưới nhé!


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í