Có lẽ bạn đã từng bấm nút trước thang máy một lần rồi vẫn thấp thỏm bấm thêm vài lần nữa. Đèn đã sáng mà bấm thêm cũng chẳng có hai thang máy đến. Ngược lại, trên cửa sổ thanh toán trực tuyến bạn cũng từng bấm nút hai lần rồi hồi hộp sợ bị trừ tiền hai lần. Cùng một thao tác mà bên thì an toàn, bên thì nguy hiểm — khái niệm giải thích lý do đó chính là tính lũy đẳng.
Tính lũy đẳng (Idempotence) là tính chất khiến một thao tác dù làm một lần hay nhiều lần cũng không đổi thêm gì so với kết quả của lần đầu. Nói dễ hiểu là “làm lại thì việc đã xong cũng không phình to thêm”, và trong thế giới máy tính — nơi kết nối đứt và yêu cầu trùng thường xuyên — khái niệm này được coi là đặc biệt quan trọng.
Trong tài liệu này chúng ta sẽ lần lượt xem tên gọi tính lũy đẳng đến từ đâu, các ví dụ đời thường phân chia ra sao, HTTP biến khái niệm này thành cam kết thế nào, cùng thiết kế xử lý yêu cầu trùng một cách an toàn và những hiểu nhầm phổ biến.
Lẽ của thao tác làm hai lần cũng như một lần
Nghĩa là cùng một lực — cái tên mượn từ toán học

Thuật ngữ này được biết là do nhà toán học Mỹ Benjamin Peirce đặt ra năm 1870 trong bài viết về đại số kết hợp tuyến tính (Linear Associative Algebra). Nó ghép idem nghĩa là “giống nhau” với potence nghĩa là “lực”, nên nghĩa đen là “có cùng một lực”. Peirce gắn tên này cho các đối tượng đại số vẫn là chính chúng khi bình phương hoặc nâng lên lũy thừa cao hơn.
(1) Định nghĩa toán học
Nghĩa là kết quả áp dụng hàm f hai lần bằng kết quả áp dụng một lần, viết là f(f(x)) = f(x).
(2) Các hàm tiêu biểu
Giá trị tuyệt đối, hàm làm tròn xuống bỏ phần thập phân, hàm làm tròn lên, và hàm đồng nhất trả về đúng đầu vào đều có tính chất này.
(3) Nghĩa sang thế giới máy tính
Dù là thao tác có tác dụng phụ, nếu trạng thái hệ thống sau nhiều lần gọi giống sau một lần gọi thì vẫn gọi là lũy đẳng.
Điểm cần chú ý là tiêu chí không phải “phản hồi giống nhau” mà là “trạng thái không đổi thêm nữa”. Bỏ lỡ khác biệt này thì dễ lẫn tính lũy đẳng với các khái niệm khác.
Phân biệt lũy đẳng và không lũy đẳng bằng các nút đời thường

Nút gọi thang máy hay nút đèn giao thông dành cho người đi bộ, lần bấm đầu đưa vào trạng thái “đang yêu cầu”, và trước khi yêu cầu đó được xử lý thì bấm thêm cũng chẳng có gì xảy ra. Nút đóng cửa cũng đóng theo lịch cố định nên bấm bao nhiêu lần cũng như nhau. Nhưng nút mở cửa mỗi lần bấm lại kéo dài thời gian chờ, nên không lũy đẳng.
- Tra cứu (Lookup): Đọc tên và địa chỉ khách hàng không đổi dữ liệu nên là lũy đẳng.
- Gán (Assignment): Yêu cầu đổi địa chỉ thành một giá trị cụ thể, gửi bao nhiêu lần địa chỉ cuối cùng cũng giống nhau nên là lũy đẳng.
- Đặt hàng (Order Placement): Yêu cầu đặt hàng có thể tạo ra số đơn bằng số lần gửi nên không lũy đẳng.
- Hủy (Cancellation): Yêu cầu hủy một đơn cụ thể, gửi bao nhiêu lần đơn vẫn ở trạng thái đã hủy nên là lũy đẳng.
Cùng là “bấm nút” nhưng số phận chia đôi tùy thao tác ấy định trạng thái hay cộng thêm gì vào trạng thái.
Cam kết theo từng phương thức mà HTTP đặt ra

Tài liệu chuẩn HTTP (RFC 9110) — quy ước của web — định nghĩa phương thức là lũy đẳng khi hiệu ứng có chủ đích lên máy chủ khi gửi cùng một yêu cầu nhiều lần giống khi gửi một lần.
(1) Yêu cầu đọc
GET, HEAD, OPTIONS, TRACE là các phương thức an toàn chỉ yêu cầu đọc và đều lũy đẳng.
(2) PUT và DELETE
PUT đặt trạng thái đối tượng theo nội dung đã gửi, DELETE xóa đối tượng. Gửi bao nhiêu lần trạng thái kết thúc cũng giống nhau nên được quy định là lũy đẳng.
(3) POST và PATCH
POST để phía nhận quyết định xử lý nội dung thế nào nên gửi hai lần có thể sinh hai đơn hàng. PATCH cũng không bảo đảm tính lũy đẳng.
Chỗ phân biệt này thực sự được dùng là khi thử lại. Khi kết nối đứt và không đọc được phản hồi, yêu cầu lũy đẳng thì máy khách có thể tự gửi lại; còn yêu cầu không lũy đẳng, nếu không có cách biết yêu cầu gốc chưa được áp dụng thì chuẩn khuyến nghị tốt hơn là không tự thử lại. Hơn nữa lũy đẳng không phải cam kết phản hồi giống nhau, nên lần xóa thứ hai có thể trả về phản hồi “không có” khác lần đầu mà vẫn ổn.

Thiết kế xử lý yêu cầu trùng và những hiểu nhầm phổ biến

Vậy khi buộc phải gửi lại POST không lũy đẳng thì làm sao? Cách được dùng rộng rãi là khóa tính lũy đẳng gắn giá trị riêng cho mỗi yêu cầu. Dịch vụ thanh toán Stripe cho phép gắn tiêu đề Idempotency-Key vào yêu cầu. Khóa dài tối đa 255 ký tự, khuyến nghị dùng giá trị kiểu UUID ngẫu nhiên, và yêu cầu đến lại với cùng khóa sẽ trả nguyên mã trạng thái và thân của yêu cầu đầu. Khóa có thể bị xóa sau ít nhất 24 giờ, và cùng khóa mà nội dung khác thì bị xử lý như lỗi. Hệ thống xử lý sự kiện cho cùng một kết quả dù thông điệp đến hơn một lần cũng đứng trên cùng ý tưởng đó.
- Phản hồi (Response): Lũy đẳng không có nghĩa phản hồi giống nhau, mà nghĩa là trạng thái máy chủ không đổi thêm nữa.
- Tác dụng phụ (Side Effect): Không tính cả việc máy chủ ghi lại yêu cầu; chỉ lấy hiệu ứng có chủ đích làm chuẩn.
- Hợp thành (Composition): Ghép các bước lũy đẳng cũng không bảo đảm toàn bộ là lũy đẳng.
- Ví dụ (Example): Khi biến bằng 3, chạy một lần “đọc, đổi thành 5, đọc” ra 3 và 5; lần thứ hai ra 5 và 5.
Việc làm cho bấm hai lần vẫn ổn là thiết kế hệ thống tự an toàn, thay vì trông chờ sự cẩn thận của người dùng. Thiết kế thao tác từ đầu sao cho làm lại vẫn ra cùng kết quả — đó là bước đầu của tính lũy đẳng.