Idempotency keys
Trace a client request and design an HTTP contract with clear failure, retry and pagination behavior.
THE IDEA
Idempotency keys
An idempotency key lets the server recognise a retry of the same intended operation. Scope it to the caller and the operation, remember the payload it first came with, and record the effect and the key atomically. A repeat with the same payload returns the original result and creates nothing; a repeat with a different payload is a conflict and must not silently create a second order.
A key identifies one intended operation across retries. Scope it to the caller and the operation, compare the payload on a repeat, and record the effect and the key in the same atomic step.
Predict first. Should a repeated key with a different purchase payload silently create a second order?
Chapter context
Clients communicate through name resolution, connections, transport security and application protocols. API semantics determine what a caller can safely infer or repeat when a network interruption hides an operation's outcome.
TRY IT YOURSELF
Part 1. Do it hands-on
Trace repeats by hand, then write an idempotent store.
A key identifies one intended operation across retries. Scope it to the caller and the operation, compare the payload on a repeat, and record the effect and the key in the same atomic step.
- To do: User A sends create-order with key op-7 and payload order-1, then (a retry) the same key and payload again. Orders created?
- To do: Then user A sends key op-7 with a DIFFERENT payload (order-2). Should the server create it, replay the first, or reject? Type create, replay or reject
- To do: User B (a different caller) sends key op-7 with payload order-9. Keys are scoped to the caller. Total orders now across A and B?
- To do: Should a repeated key with a different purchase payload silently create a second order? Type yes or no
Part 2. Do it in code
- class IdempotentStore with self.orders (list of created orders) and self.keys (dict mapping (caller, key) → (payload, order)).
- create(caller, key, payload): the effect AND the key record are written together. First time for (caller, key): create the order {"caller", "payload"}, remember it, return ("created", order). Same caller, same key, same payload: return ("replayed", the original order) and create nothing. Same caller and key but a different payload: return ("conflict", None) and create nothing. A different caller using the same key is a separate operation.
- count(): number of orders created.
- key_for(caller, operation, request_id): a stable scoped key string "caller:operation:request_id".
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Should a repeated key with a different purchase payload silently create a second order?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
For k sequential calls with round-trip latency r, the network component alone is approximately k*r under a simple fixed-RTT model. Independent parallel calls have an ideal max-path cost instead. This excludes connection setup, payload transfer, queueing and application computation.
Worked example
Three required sequential remote calls with 40 ms round trips consume about 120 ms of round-trip time before server work. For a separate write example, processing request key op-7 twice should create one logical result if the idempotency contract is implemented atomically. The calculation below is an offline model, not a distributed storage implementation.
Reference code
The following example uses the Python standard library and does not call a remote service.
assert 3*40 == 120
seen={}
def create(key,value):
if key in seen:
if seen[key] != value: raise ValueError('key reused with different intent')
return seen[key]
seen[key]=value
return value
assert create('op-7','order-1') == create('op-7','order-1')
assert len(seen) == 1
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
A timeout is not proof of rollback. HTTP transport security does not authorize object access. An in-memory deduplication dictionary does not supply durability or atomicity across servers.