SkillPilotAI

Idempotency and Exactly-once Processing

Whether the same request arrives once or 10 times, the result is applied only once; that is idempotency.

34.1 Why is it used?

  • After a network timeout the client does not know whether the request reached the server, so it retries.
  • Without idempotency, a retry can cause a double payment, a double order or a duplicate SMS.
  • Queues like Kafka and SQS give at-least-once delivery, meaning the same message can reach the consumer twice.
  • A user's double-click or an app's auto-retry also sends a duplicate request.
  • Exactly-once is practically impossible at the network level, so at-least-once delivery + idempotent processing = effectively exactly-once.

34.2 How it works

  • Idempotency Key: the client generates a UUID for every unique operation and sends it in a header, for example Idempotency-Key: 7f3a...; the same key is used on retry.
  • The server checks the key first: if not found, process and store the response; if found, return the stored response.
  • Dedup table: a DB table (idempotency_key PRIMARY KEY, status, response, created_at). The unique constraint blocks a duplicate insert.
  • In-progress lock: if 2 parallel requests arrive with the same key, give the second a 409 Conflict until the first completes.
  • Naturally idempotent operations: SET balance=100, DELETE and PUT are idempotent; balance=balance+100 and POST are not.
  • Consumer dedup: save the message_id in a processed_messages table in the same DB transaction as the business update.
  • Kafka exactly-once: an idempotent producer (enable.idempotence=true) + transactions give exactly-once semantics in a Kafka-to-Kafka pipeline.
  • TTL: expire idempotency keys after 24 hours or a few days; Stripe keeps keys for at least 24 hours.
Payment retry: same key, only one charge
  1. 1.Swiggy app: Pay Rs 450, key = abc-123 (a UUID, generated on the client side)
  2. 2.Order Backend: POST /charges, header Idempotency-Key: abc-123 -> Payment Provider (Stripe API)
  3. 3.The provider charges the card, but a timeout occurs while the response is coming back
  4. 4.The backend retries with the same key abc-123; the provider checks its idempotency_keys table (abc-123 -> success, TTL 24h)
  5. 5.Key found -> the old response is returned and NO new charge is created; result: money deducted once, one order created (same key + different amount -> Stripe error)
  6. 6.Inside the server: Redis SET key NX EX 86400 (fast filter, the first writer wins) -> DB transaction (BEGIN ... COMMIT): INSERT idempotency_keys UNIQUE + INSERT order + charge + save response
  7. 7.Duplicate key: status done -> stored response 200; in-progress -> 409 Conflict until the first one completes

at-least-once delivery (Kafka / SQS) + idempotent consumer (save message_id in processed_messages in the same DB transaction) = effectively exactly-once

34.3 Tools

ToolTypeHow it is used
Redis (SET NX EX)In-memory storeA fast dedup check with SET key value NX EX 86400; the first writer wins, the rest are treated as duplicates.
PostgreSQL / MySQL unique constraintRelational DBA UNIQUE index on the idempotency_keys table; the business row and the key are inserted in one transaction, the strongest guarantee.
Apache Kafka (idempotent producer + transactions)Event streamingPrevents duplicate writes from the producer and makes read-process-write atomic.
AWS SQS FIFOManaged queueDrops duplicate messages within a 5-minute window using MessageDeduplicationId.
DynamoDB conditional writeNoSQL DBPutItem with the attribute_not_exists(pk) condition; on a duplicate you get ConditionalCheckFailed.
Stripe API Idempotency-KeyPayment API patternStripe accepts an Idempotency-Key header on POST requests and returns the same result for the same key.

34.4 Comparison: Redis SETNX vs DB Unique Constraint vs SQS FIFO dedup

PointRedis SET NXDB Unique ConstraintSQS FIFO Dedup
SpeedSub-millisecondA few millisecondsWith queue latency
DurabilityMemory; keys can be lost on failoverDurable on diskAWS managed, durable
Atomic with business dataNo, it is a separate systemYes, same transactionNo, only at message level
Dedup windowAs long as the TTL you setUntil the row is deletedFixed 5 minutes
ScalingEasy with a clusterDB write load increasesFIFO throughput limits apply
Best forFast first-level filter, API gatewayPayments, orders where correctness is criticalStopping duplicate sends on the producer side

34.5 When to choose which

  • DB unique constraint: money, orders, ledger where you need 100% correctness.
  • Redis SET NX: a fast duplicate filter in a high-traffic API, the first layer in front of the DB check.
  • SQS FIFO / Kafka idempotent producer: to reduce duplicates at the queue level; still keep consumer dedup.
  • Best combo: client idempotency key + Redis fast check + DB unique constraint as the final guard.

34.6 Real-world example

Real world: a Stripe-style payment retry

  1. In a Swiggy-like app the user taps Pay Rs 450, and the app creates a UUID idempotency key.
  2. The backend calls POST /charges with the header Idempotency-Key: abc-123.
  3. The payment provider charges the card, but a network timeout occurs while the response is coming back.
  4. The backend does not know whether the charge happened, so it retries with the same key abc-123.
  5. The provider sees that abc-123 was already processed, so it does not create a new charge and returns the old success response.
  6. The user's money was deducted only once, and only one order was created.
  7. If the retry sends a different amount with the same key, Stripe returns an error, because a key is bound to one specific request.

34.7 Interview tips

  • Generating the key on the server: a retry creates a new key and dedup fails. The key must be generated on the client side.
  • Doing the business action first and saving the key later: a crash in between causes a duplicate. Do both in the same transaction.
  • Assuming Kafka exactly-once is end-to-end: for an external DB or API call you still need consumer-side idempotency.
  • Interview tip: remember this line: 'at-least-once + idempotent consumer = effectively exactly-once'.