Nếu bạn từng vận hành một cụm etcd hay CockroachDB, có lẽ bạn đã gặp tình huống log Raft phình to đến mức chiếm hết bộ nhớ. Raft là thuật toán đồng thuận phổ biến nhất trong hệ phân tán hiện đại, nhưng bản thân nó có một điểm yếu cốt lõi: log tăng không giới hạn. Bài viết này giải thích cách các hệ thống thực tế giải quyết vấn đề đó bằng snapshot.
Vấn đề: log không thể phình mãi
Trong Raft, mọi thay đổi trạng thái đều được ghi vào log dưới dạng entry. Khi hệ thống chạy hàng tháng, hàng triệu entry tích lũy sẽ gây ra hai vấn đề nghiêm trọng: tốn bộ nhớ và thời gian khởi động lại kéo dài vì phải phát lại toàn bộ log từ đầu. Hãy hình dung bạn có một cuốn sổ ghi chép mọi giao dịch ngân hàng từ ngày mở tài khoản, trong khi bạn chỉ cần biết số dư hiện tại.
Snapshot: chụp trạng thái, xóa log cũ
Giải pháp đơn giản nhất là tạo snapshot. Mỗi node trong cụm Raft sẽ định kỳ "chụp" lại toàn bộ trạng thái hiện tại của state machine vào bộ nhớ bền vững, rồi xóa sạch mọi log entry trước thời điểm đó. Vì state machine có tính xác định, tức đầu vào giống nhau sẽ cho cùng một kết quả, việc khôi phục từ snapshot tương đương phát lại toàn bộ log, nhưng nhanh hơn nhiều lần.
Câu hỏi quan trọng là khi nào nên tạo snapshot. Chính sách phổ biến nhất là theo kích thước log: khi số entry vượt ngưỡng nhất định (ví dụ 10.000 entry), hệ thống tự động kích hoạt snapshot. Một số hệ thống kết hợp thêm điều kiện thời gian để tránh tạo snapshot quá thường xuyên khi ghi nhiều.
InstallSnapshot RPC: cứu follower chậm
Khi một follower bị tụt lại quá xa, leader không thể gửi log entry bình thường vì những entry đó đã bị xóa sau snapshot. Lúc này, leader dùng RPC InstallSnapshot để gửi toàn bộ snapshot cho follower. Follower nhận snapshot, thay thế hoàn toàn state machine của mình, rồi tiếp tục nhận log entry mới từ vị trí sau snapshot.
Đây là đánh đổi quan trọng: snapshot giúp tiết kiệm bộ nhớ trên leader nhưng tạo ra "chi phí" lớn khi phải truyền toàn bộ snapshot qua mạng cho follower chậm. Trong hệ thống có dữ liệu lớn, một snapshot có thể nặng hàng GB.
Snapshot gia tăng: giảm áp lực I/O
Mỗi lần chụp toàn bộ state machine đều tiêu tốn đáng kể tài nguyên I/O. Snapshot gia tăng chỉ ghi lại các thay đổi kể từ lần chụp trước, nhờ đó giảm đáng kể lượng dữ liệu phải ghi. Kỹ thuật này tương tự cơ chế sao lưu gia tăng trong cơ sở dữ liệu truyền thống.
Thực tế trong etcd và CockroachDB
Thư viện Raft của etcd là bản triển khai được sử dụng rộng rãi nhất, phục vụ hàng chục nghìn cụm mỗi ngày cho Kubernetes, Docker Swarm và nhiều hệ thống khác. Trong etcd, khi gửi snapshot thất bại, ứng dụng phải báo lại cho leader thông qua ReportSnapshot() để leader chuyển sang chế độ probing, tránh để follower bị kẹt vĩnh viễn.
CockroachDB xử lý phức tạp hơn vì mỗi Range là một nhóm Raft riêng biệt. Họ xây dựng hàng đợi snapshot ở cấp Store để kiểm soát số lượng snapshot được gửi đồng thời, tránh quá tải hệ thống. Trước khi có hàng đợi này, snapshot thường khiến cả cụm gần như ngừng hoạt động.
Tóm lại
Nén log bằng snapshot là cơ chế không thể thiếu trong bất kỳ hệ phân tán nào dùng Raft. Hiểu rõ thời điểm tạo snapshot, cách truyền snapshot cho follower chậm, và đánh đổi giữa snapshot toàn phần và snapshot gia tăng sẽ giúp bạn vận hành hệ thống ổn định hơn trong thực tế.





