Tuần này DeepSeek vừa tung ra bản V4-Flash giữa lúc cuộc đua hạ tầng AI của Trung Quốc nóng lên từng ngày. Đằng sau mỗi mô hình được huấn luyện và phục vụ ở quy mô đó là hàng nghìn server gọi nhau qua mạng, và mạng thì luôn có lúc chập chờn. Request gửi đi, server xử lý xong, nhưng gói tin trả lời bị rớt giữa đường. Client không biết kết quả nên làm điều duy nhất hợp lý: gửi lại. Vấn đề là nếu request đó là trừ tiền khách hàng hoặc gửi email xác nhận đơn hàng, gửi lại có thể khiến việc đó xảy ra hai lần.

Đây chính là lý do tồn tại khái niệm idempotency, tức tính chất một thao tác dù được gọi một lần hay mười lần cũng cho ra đúng một kết quả cuối cùng, không có tác dụng phụ chồng chất. Hãy nghĩ đến nút bấm thang máy: bấm mười lần tầng 5 thì thang máy vẫn chỉ dừng ở tầng 5 một lần, chứ không chạy lên chạy xuống mười vòng.

At-least-once delivery: Cái giá của việc không làm mất dữ liệu

Trong các hệ thống công nghệ dùng message queue như Kafka hay RabbitMQ, người ta gần như luôn chọn cơ chế at-least-once delivery: thà giao message thừa còn hơn làm mất message. Nghe có vẻ an toàn, nhưng đổi lại, dịch vụ phía nhận phải tự bảo đảm không xử lý trùng. Muốn có cảm giác giống exactly-once semantics, thứ về mặt lý thuyết gần như không thể đảm bảo tuyệt đối trong hệ phân tán vì bài toán Two Generals kinh điển, người ta buộc phải kết hợp at-least-once delivery với xử lý idempotent ở tầng ứng dụng.

Idempotency key và deduplication table

Cách phổ biến nhất, được Stripe áp dụng cho các API thanh toán, là bắt client tự sinh một idempotency key duy nhất cho mỗi lần thử thao tác, thường là UUID, rồi gửi kèm request. Server lưu key này vào một deduplication table cùng với kết quả xử lý. Lần đầu thấy key lạ, server xử lý bình thường rồi ghi lại. Lần sau nếu thấy key đã tồn tại, server không chạy lại logic nữa mà chỉ trả về đúng kết quả cũ đã lưu. Điểm mấu chốt thường bị bỏ sót là việc ghi key vào bảng dedup và thực thi thao tác chính phải nằm trong cùng một transaction. Nếu tách rời hai bước này, race condition vẫn có thể xảy ra khi hai request trùng key đến gần như cùng lúc.

Idempotent tự nhiên và idempotent nhân tạo

Không phải thao tác nào cũng cần idempotency key. Lệnh UPDATE user SET status = 'active' vốn đã idempotent tự nhiên, chạy bao nhiêu lần kết quả cuối vẫn vậy. Nhưng lệnh UPDATE balance SET balance = balance + 100 thì không, mỗi lần chạy lại cộng thêm 100. Với những thao tác kiểu cộng dồn, gửi thông báo hay tạo bản ghi mới như vậy, ta phải chủ động tạo ra tính idempotent bằng key và bảng dedup, gọi là idempotent nhân tạo. Quy tắc thực dụng khi thiết kế hệ thống: nếu retry là điều chắc chắn sẽ xảy ra, đừng coi idempotency là tính năng thêm vào sau, hãy thiết kế nó ngay từ đầu.