payments
Idempotency Keys
A conditional write that makes a retried request safe.
One table, one job: let the first attempt at a request through and reject every retry. A PutItem with attribute_not_exists on the key is the whole mechanism - the write either claims the key or fails, and the caller learns which without a read-then-write race.
The model
IdempotencyKey
A claimed request key and the outcome it produced.
- pk
- IDEMPOTENCY#<key>
Attributes: status (S), resultId (S), claimedAt (N), expiresAt (N)
Access patterns
- PutItemClaim a request key
Write the key only if nobody has claimed it, so a retry is refused rather than duplicated.
- UpdateItemRecord the outcome
Move a claimed key from in_progress to its final state.
- GetItemRead a previous outcome
Fetch what the first attempt produced, to return it again.
Design notes
The condition is the mechanismwhy
attribute_not_exists(PK) makes the write itself the mutual exclusion. Reading first and writing if absent looks equivalent and is not: two concurrent requests both read nothing and both write. The condition is evaluated where the item lives, so exactly one wins.
The claim is a design here, not a live demonstrationtrade-off
The preview console runs the read below, not the conditional write: the resolver does not yet emit a runnable guarded write, so there is no button here that races two claims and shows one losing. The engine itself does support the mechanism - a conditional put rejects a duplicate with 'The conditional request failed' - so this is a gap in what the page can demonstrate, not in what DynamoDB does.
A retry can arrive in three stateswhy
The seeded rows stage all three. A succeeded key returns its stored result. A failed key has to say whether it may be reclaimed, or a caller retries forever against a key that will never succeed. An in_progress key is the awkward one: the first attempt is still running, so there is no result to return and no safe way to start a second - the honest answer is a retryable conflict, not a fabricated success.
Claim before doing the work, not afterwhy
The key is written in_progress before the side effect runs, then updated with its outcome. Claiming afterwards would leave a window where a retry starts a second charge, which is the failure the whole pattern exists to prevent.
A failed condition is a normal outcometrade-off
The rejected retry is not an error to alert on: it is the pattern working. The caller reads the existing key and returns its stored result, so the client sees one answer no matter how many times it asks.
Taught in the course
- The key is the address - GetItem needs the whole primary key: partition and sort.
- Claim it, or fail - A ConditionExpression lets a write happen only if the data still allows it.
Fetch what the first attempt produced, to return it again.
Run an operation to see the raw engine response.
The in-browser engine is a preview build. Transactions, vector search, streams, tags and TTL are among the operations it doesn't implement yet.