Context
It can be important to return exactly the same response every time for the same request. Due to underlying state changes, a retried request might not naturally return the exact same response every time.
Example
The Stripe API models the status of a payout as a state machine with the following states: paid, pending, in_transit, canceled or failed. Some operations, e.g. cancel, are only valid when the payout is in certain states. Retries of successful cancel requests should return the same successful response, even if the status of the payout has since changed.
Problem
How do we return the same response, even when the underlying state changes?
Solution
Before sending a response to the client, write it to a database, in the same transaction as the state changes that produced it. This could be indexed by an idempotency key in a unique column. When handling a request, first check whether a response record exists for this request. If it does, simply return that response.
If two requests race, both might find no response record. The unique index means only one of their transactions can commit. The other fails, and can then re-read and return the response from the winning request.
This is a special case of a recovery point.
See also
Related
- ACID transaction – Perform multiple writes, such that either all of them or none of them succeed
- At-most-once guard – Write to a system at most once
- Idempotency key lock – Protect against concurrent retries
- Idempotency key – Identify identical requests
- Recovery point – Record current progress to allow recovery with minimal rework
- Reject duplicate requests – Return an error when a duplicate request is detected