Design durable idempotency for retryable mutations
Reason about ambiguous mutation outcomes, transactional receipts, concurrent retries, retention, and external effects.
Durable idempotency for retryable mutations
Use this pattern when a client may retry a mutation after an ambiguous timeout.
- Have the client generate one unpredictable idempotency key per logical operation and reuse it for every retry. Scope the key to the authenticated tenant or actor and operation. Store a canonical request fingerprint so different inputs under the same key are rejected.
- Enforce a unique database constraint on the scoped key. In one database transaction, claim the key, apply the business mutation, store a replayable outcome or stable result reference, and commit. Do not expose a successful outcome before commit.
- On a duplicate key, wait for an in-flight transaction to commit or roll back. If it commits, compare fingerprints and return its stored outcome without running the mutation. If it rolls back, a retry may become the claimant. A standalone preflight lookup is insufficient to control a race.
- Walk both crash windows: before commit, receipt and effect roll back together; after commit but before response, receipt and effect survive, so the retry replays the result.
- State the retention horizon. Deleting a receipt permits later reuse of the key and a second effect; retain it across the full retry window or enforce a permanent business operation identifier.
- If an effect leaves the transaction boundary, use a transactional outbox and an idempotent downstream consumer or equivalent coordinated guarantee. A local receipt alone cannot atomically protect an external call.
Distinguish a reasoned design from executed tests. Validate transaction isolation, uniqueness conflicts, crash windows, and external delivery behavior in the actual implementation.