Pattern: idempotency keys
This course builds an idempotency guard in Rails one piece at a time. It starts with a check-then-store key that lets five concurrent threads create duplicate XP records. Reordering the steps into claim, work, complete lets the unique index decide before any work runs. Later lessons cover what losing requests should do, using 409 Conflict as Stripe does. They also cover fingerprinting parameters and rejecting mismatches with 422, storing claims in Postgres or Solid Cache, and choosing random or derived keys.
What you'll learn
- ✓Why a check-then-store key lets concurrent requests duplicate the work
- ✓The claim, work, complete order with a pending status
- ✓Polling, blocking or failing fast with 409 when a request loses the claim
- ✓Request fingerprints and 422 responses for keys reused with different parameters
- ✓Trade-offs between a Postgres table and Solid Cache for storing claims
- ✓Choosing between random UUID keys and keys derived from stable fields
Duplicate keys vs. duplicate work
Lesson 3 of the mid-level course "Service objects: idempotency" showed a check-then-store shape: look up the idempotency key, and if it's missing, do the work, then save the key. It used a hypothetical payment service. Here is the same shape applied to a hypothetical XP award:
Read the full lesson free →Curriculum
7 lessonsDuplicate keys vs. duplicate work
Five concurrent threads run a check-then-store guard with one key and create four XP records instead of one.
Reserve before you work
Claiming the key in a pending state before the work lets the unique index stop duplicate XP records.
What the loser does
Compares polling, blocking and failing fast for losing requests, with Stripe and an IETF draft both using 409 Conflict.
The key means the request, not just the operation
Storing a fingerprint of the request parameters lets the guard reject a reused key with a 422.
Where the reservation lives
Compares claims in a Postgres table and in Solid Cache on durability, audit history and expiration.
Designing the key itself
Random client-generated keys fail when the client reloads, while keys derived from stable fields survive crashes and retries.
Putting idempotency keys together
Reassembles the full guard, explains where the rescue and retry sit, and sets a plain Ruby challenge.