Context
Sometimes side effects by a system cannot be ensured to happen exactly once, e.g. because the system does not use an idempotency key. Writing to the system more than once might cause repeated side effects.
Prerequisites
Producing the side effect zero times is acceptable, but more than once is not. It is not necessary to know whether the operation succeeded.
Example
A company is using a payment system which does not implement idempotency keys. The company should not try to initiate a payment more than once, because it might double charge the customer.
Problem
How do we ensure that a side effect happens at most once?
Solution
Write a record to a database before performing the operation. If the record already exists, do not perform the operation. The operation will either succeed or fail. Subsequent retries will see the guard record and not attempt the operation again.
An atomic read-then-write should be used to write the record. In the following example, the query will only return a result if the row does not already exist.
INSERT INTO guards (idempotency_key)
VALUES ('some-key')
ON CONFLICT (idempotency_key) DO NOTHING
RETURNING idempotency_key;
This pattern can lead to uncertainty. If the guard record exists, did the operation succeed or fail? It can be helpful to combine with a recovery point or a response record to record the result, but this might not always succeed.
Related
- Atomic read-then-write – Concurrently write data based on current state
- Idempotency key – Identify identical requests
- Recovery point – Record current progress to allow recovery with minimal rework
- Response record – Return the same response for every retry