Idempotency Keys and Retry Semantics

How to make mutation retries safe by binding idempotency keys to parameters, results, and dedupe windows.

Software ยท Distributed Systems

Retries turn partial failure into duplicate execution unless the operation is designed for them. Idempotency makes repeated requests with the same intent produce one logical effect.

Idempotence

An operation is idempotent when applying it more than once has the same logical effect as applying it once:

\[f(f(x)) = f(x)\]

GET is normally idempotent because it does not create a new resource. POST /charges is not idempotent unless the API adds an idempotency mechanism.

Request key contract

An idempotency key identifies one client intent. The server stores the first completed result for that key and returns the same result to later retries.

A robust record stores:

Field Purpose
key client retry identity
parameter hash prevents key reuse with different input
operation name avoids cross-endpoint collision
status in progress, completed, failed
response summary stable replay result
resource id reconciliation path
expiry bounded storage growth

Parameter binding matters. If the same key is reused with different parameters, accepting it can hide a client bug and corrupt intent.

Race handling

Two identical requests can arrive concurrently. The server needs an atomic claim:

INSERT INTO idempotency_keys (key, param_hash, status)
VALUES (?, ?, 'in_progress')
ON CONFLICT DO NOTHING;

Only the request that creates the claim executes the mutation. Others wait, poll, or receive a conflict response depending on API semantics.

The mutation and idempotency result should commit atomically, or recovery must repair incomplete records. A common pattern is one database transaction containing the idempotency row, mutation, and response metadata.

Retry semantics

Retries need budgets and backoff. A safe mutation can still overload the service if every client retries aggressively.

Classify failures:

Failure Retry behavior
validation error do not retry without changed input
auth failure retry only after credential refresh
timeout before result known retry with same idempotency key
409 in progress retry after delay or poll status
overload retry with backoff and budget

Practical checks

  • Idempotency keys are required for externally retried mutations.
  • Keys are high entropy and contain no secrets.
  • Same key plus different parameters is rejected.
  • Concurrent same-key requests produce one mutation.
  • Result replay includes failed results when the operation began and failed deterministically.
  • Expiry policy matches the maximum client retry window.
  • Metrics track key conflicts, replays, mismatched parameters, and in-progress age.

Design guidance

Make retries safe at the API contract, not at the client callsite. If the operation can commit side effects before the client sees the response, require an idempotency key or expose a reconciliation read path.

Source anchors