Quản Lý Dữ Liệu Cache Lạnh Trong Lua - nói dối e blog

Quản Lý Dữ Liệu Cache Lạnh Trong Lua

Hôm nay, một đồng nghiệp đã chia sẻ với tôi một lỗi thú vị mà anh ấy phát hiện gần đây. Tôi cảm thấy vấn đề này rất đáng để thảo luận sâu.

Bối cảnh yêu cầu:
Trong hệ thống của chúng tôi, một số dữ liệu được nạp từ bộ nhớ ngoài (cơ sở dữ liệu) vào bộ nhớ trong. Do cân nhắc hiệu năng, chúng tôi không cần thiết phải ghi dữ liệu lại ngay lập tức mỗi khi có thay đổi. Thay vào đó, dữ liệu sẽ được ghi lại định kỳ khi nó trở nên “lạnh” (ít được truy cập).

Dạng mẫu điển hình:
Đây là một mô-đun cache lưu trữ dữ liệu nghiệp vụ của người chơi, có thể tra cứu qua uuid. Khi nghiệp vụ cần truy cập dữ liệu, mô-đun cache sẽ tải dữ liệu từ cơ sở dữ liệu, sau đó cung cấp bảng dữ liệu cho các thành phần sử dụng. Nếu dữ liệu đã tồn tại trong cache, mô-đun sẽ trực tiếp trả về bản sao trong bộ nhớ.

Yêu cầu đặc biệt:
Khi dữ liệu không được truy cập trong thời gian dài, mô-đun cần có cơ chế ghi dữ liệu đó trở lại cơ sở dữ liệu.

Môi trường triển khai:
Hệ thống được xây dựng bằng Lua, nơi mô-đun cache và logic xử lý dữ liệu chia sẻ cùng một máy ảo (VM). Thách thức nằm ở việc xác định chính xác thời điểm dữ liệu không còn được tham chiếu, bởi các thành phần nghiệp vụ có thể giữ giữ liệu trong thời gian dài.


Giải pháp nguyên thủy

Mô-đun cache được hiện thực hóa bằng một bảng ánh xạ uuid → dữ liệu. Quy trình làm việc cơ bản:

  1. Khi tải dữ liệu: Kiểm tra sự tồn tại trong cache → Nếu chưa có, nạp từ cơ sở dữ liệu → Cập nhật thời gian truy cập gần nhất.
  2. Quy trình ghi dữ liệu xuống lưu trữ:
    • Lấy dữ liệu “lạnh” (vượt quá thời gian chờ) từ hàng đợi
    • Xóa khỏi cache
    • Khóa uuid (ngăn chặn việc nạp đè)
    • Ghi xuống cơ sở dữ liệu
    • Mở khóa và đánh thức các yêu cầu treo

Hạn chế:
Việc xác định “lạnh” chỉ dựa trên thời gian truy cập cuối cùng, không xét đến việc dữ liệu có đang được tham chiếu hay không. Điều này dẫn đến nguy cơ dữ liệu đang bị sửa đổi trong khi quy trình ghi xuống đang diễn ra.


Giải pháp nâng cấp: Sử dụng bảng yếu (Weak Table)

Chúng tôi đã cải tiến bằng cách tận dụng cơ chế bảng yếu của Lua:

  • Khi không còn tham chiếu nào tồn tại, mục tương ứng trong bảng yếu sẽ tự động bị loại bỏ
  • Đây là thời điểm lý tưởng để ghi dữ liệu xuống, vì không có thay đổi nào đang diễn ra

Chi tiết triển khai:

  1. Thêm phương thức __gc cho các khối dữ liệu
  2. Trong quá trình thu gom rác (GC), không thực hiện ghi trực tiếp, mà chuyển dữ liệu vào một bảng “save” chờ xử lý
  3. Bảng save được xử lý tuần tự bởi quy trình hậu kỳ

Lưu ý: Lua cho phép “hồi sinh” (resurrect) đối tượng trong quá trình GC thông qua việc đưa lại vào không gian VM trước khi bị xóa hoàn toàn.

Tối ưu hóa:

  • Chỉ kích hoạt cơ chế bảng yếu khi kích thước cache vượt ngưỡng nhất định
  • Sử dụng hai bảng cache riêng biệt: một bảng mạnh (strong) và một bảng yếu (weak), chuyển dữ liệu “lạnh” từ bảng mạnh sang bảng yếu định kỳ

Vấn đề phát sinh

Một bug nghiêm trọng đã được phát hiện:

  • Việc loại bỏ khỏi bảng yếu và thêm vào bảng save không phải thao tác nguyên tử
  • Dẫn đến trạng thái trung gian: Dữ liệu không tồn tại trong cả cache và save
  • Nếu có yêu cầu truy cập trong trạng thái này → Kích hoạt việc nạp bản cũ từ cơ sở dữ liệu

Giải pháp sửa lỗi

Giải pháp 1: Quản lý tập hợp uuid đang xử lý

  • Tạo một bảng độc lập tracking các uuid đang tồn tại trong hệ thống
  • Chỉ xóa uuid khỏi bảng này khi dữ liệu đã được ghi xuống hoặc bị loại bỏ hoàn toàn
  • Cơ chế này đảm bảo không có việc nạp bản cũ khi dữ liệu đang trong quá trình xử lý

Giải pháp 2: Thêm tầng trung gian qua metatable

  • Sử dụng proxy table với __index__newindex trỏ đến bảng dữ liệu thực
  • Cache lưu trữ uuid → proxy object
  • Dữ liệu thực được tập trung trong bảng “all” riêng biệt
  • Quy trình ghi xuống xử lý chênh lệch giữa bảng all và cache

Lưu ý: Vẫn tồn tại vấn đề tiềm ẩn nếu nghiệp vụ giữ tham chiếu đến các bảng con. Giải pháp là thiết lập quy tắc nghiêm ngặt về cách sử dụng dữ liệu.


Kết luận

Việc quản lý cache lạnh trong môi trường Lua đòi hỏi sự cân bằng giữa hiệu năng và tính nhất quán dữ liệu. Cơ chế bảng yếu và thu gom rác cung cấp công cụ mạnh mẽ, nhưng cần được sử dụng cẩn trọng để tránh các trạng thái trung gian nguy hiểm. Giải pháp proxy table mở ra hướng tiếp cận mới, giúp tách biệt rõ ràng giữa logic cache và dữ liệu thực tế.

0%