Consider a mutation whose first attempt may commit even though its response is lost. The design must survive retries, concurrent duplicate requests, process crashes, and restarts. The key question is which durable record, uniqueness constraint, transaction boundary, and response data are minimally necessary, plus where the guarantee stops when external side effects are involved.
The minimal robust pattern is a caller-scoped idempotency key protected by a durable unique constraint, with the business mutation and durable replay result committed in the same database transaction. A request fingerprint prevents accidental key reuse with different semantics. A lost post-commit response is replayed from the record; a crash before commit leaves neither effect nor completed record. Concurrent duplicates are serialized by the uniqueness constraint. Persisting an in-progress marker in a separate transaction adds abandonment recovery and is not part of the smallest design. For nontransactional external effects, atomically enqueue an outbox event, then require downstream deduplication by that event identifier; a local database record alone cannot promise exactly-once execution at an arbitrary external system.
Across a canonicalization rollout, retain one uniqueness namespace for each caller, operation, and idempotency key; do not add the fingerprint version to that uniqueness key. Persist the request contract version, fingerprint algorithm version, fingerprint, and a version-neutral result snapshot. On retry, look up the key first and compare using the stored record version. Cross-version matches require an explicit lossless compatibility adapter into the stored version's semantic domain; requests with new nondefault meaning or lossy projection must conflict. If an old canonicalizer omitted a now-meaningful distinction and old records retained no supplementary evidence, safely distinguishing those requests is impossible, so permissive matching must not be used. Deploy readers for both versions before enabling new-version writes, and retain old comparators through the retry window.