1. 13:53 17/08/2026
Trong web/app, một yêu cầu có thể được gửi nhiều lần dù người dùng chỉ bấm một lần. Mạng chập chờn khiến trình duyệt không biết request đã đến server hay chưa, ứng dụng di động tự động thử lại khi kết nối khôi phục, hoặc người dùng nhấn nút thanh toán liên tiếp vì tưởng giao diện bị treo. Nếu server xử lý mọi request như một thao tác mới, hệ quả có thể là tạo hai đơn hàng, trừ tiền hai lần hoặc gửi cùng một email nhiều lần.
Idempotency là cách thiết kế để một thao tác có thể được thực hiện lặp lại mà kết quả cuối cùng vẫn tương đương với thực hiện một lần. Khái niệm này đặc biệt quan trọng với các thao tác tạo dữ liệu hoặc thay đổi trạng thái, chẳng hạn tạo đơn, đăng ký gói dịch vụ, nạp tiền và cấp mã giảm giá.
Cách triển khai phổ biến là yêu cầu client gửi một idempotency key, thường là một chuỗi ngẫu nhiên duy nhất cho mỗi thao tác nghiệp vụ. Ví dụ, khi người dùng xác nhận đơn hàng, ứng dụng tạo key order-submit-8f3... và gửi key này trong header hoặc phần dữ liệu request. Server lưu key cùng kết quả xử lý. Nếu nhận lại request có cùng key, server không tạo đơn mới mà trả lại kết quả trước đó.
Một quy trình có thể gồm các bước sau:
- Client tạo key mới khi bắt đầu một thao tác, không tạo key mới cho mỗi lần retry của cùng thao tác.
- Server kiểm tra key trong kho lưu trữ idempotency.
- Nếu key chưa tồn tại, server ghi nhận trạng thái đang xử lý rồi thực hiện nghiệp vụ.
- Khi hoàn tất, server lưu trạng thái, mã phản hồi và dữ liệu cần trả về.
- Nếu request lặp lại, server trả lại kết quả đã lưu thay vì chạy nghiệp vụ lần nữa.
Chỉ lưu key thôi chưa đủ. Cần gắn key với người dùng, tài khoản hoặc loại thao tác để tránh việc một client dùng nhầm key của thao tác khác. Nội dung request cũng nên được kiểm tra. Nếu cùng một key nhưng lần sau gửi số tiền hoặc mã sản phẩm khác, server nên từ chối thay vì âm thầm trả về kết quả cũ. Cách này giúp phát hiện lỗi ở client sớm hơn.
Trong cơ sở dữ liệu, nên kết hợp idempotency với ràng buộc duy nhất và transaction. Ví dụ, bảng đơn hàng có thể có cột idempotency_key với unique constraint. Ràng buộc này là lớp bảo vệ cuối cùng nếu hai request đến gần như đồng thời. Với các nghiệp vụ tài chính, không nên chỉ dựa vào logic kiểm tra rồi mới insert, vì hai tiến trình có thể cùng vượt qua bước kiểm tra trước khi ghi dữ liệu.
Cũng cần quyết định rõ vòng đời của key. Với thao tác thanh toán, thời gian lưu có thể cần đủ dài để bao phủ các lần retry và quá trình đối soát. Với thao tác nhẹ hơn, có thể dùng thời hạn ngắn hơn. Không nên xóa key quá sớm nếu request cũ vẫn có khả năng được gửi lại. Khi key hết hạn, client phải hiểu rằng việc gửi lại có thể tạo một thao tác mới và cần được cảnh báo phù hợp.
Một số chi tiết thực tế thường bị bỏ quên:
- Trạng thái đang xử lý nên được phân biệt với thành công và thất bại. Request lặp lại trong lúc thao tác đầu tiên chưa xong có thể nhận phản hồi đang xử lý thay vì khởi chạy lần hai.
- Nếu server gặp lỗi trước khi lưu kết quả, cần có chiến lược phục hồi và đối soát, đặc biệt khi đã gọi sang hệ thống thanh toán bên ngoài.
- Webhook từ đối tác cũng có thể đến lặp lại; hãy lưu mã sự kiện hoặc mã giao dịch và xử lý theo cùng nguyên tắc.
- Giao diện nên vô hiệu hóa nút trong thời gian ngắn, nhưng đây chỉ là biện pháp hỗ trợ. Bảo đảm chính phải nằm ở server.
- Log nên ghi idempotency key, mã người dùng và mã nghiệp vụ để việc điều tra sự cố dễ hơn, đồng thời tránh ghi dữ liệu nhạy cảm không cần thiết.
Khi bắt đầu áp dụng, hãy chọn một luồng có rủi ro cao như tạo đơn hoặc thanh toán. Viết test cho các tình huống gửi lại cùng key, gửi đồng thời, đổi nội dung request, server timeout và lỗi giữa chừng. Idempotency không thay thế transaction hay xử lý lỗi, nhưng nó tạo thêm lớp bảo vệ quan trọng để hệ thống không biến một lần bấm thành nhiều lần thay đổi dữ liệu.