Skip to content
Optimal Lab

System design · interactive

Delivery guarantees and duplicates

A consumer reads five payment messages from a queue and saves its position (the offset) as it goes. It crashes once, right after charging pay-3. When it saves its position decides whether a message is lost or done twice.

  1. pay-1 ×1
  2. pay-2 ×1
  3. pay-3 ×2
  4. pay-4 ×1
  5. pay-5 ×1
  1. 1pay-1: charge 10
  2. 2commit offset 1
  3. 3pay-2: charge 20
  4. 4commit offset 2
  5. 5pay-3: charge 30
  6. 6crash before committing
  7. 7restart from offset 2: pay-3 is delivered again
  8. 8pay-3: charge 30
  9. 9commit offset 3
  10. 10pay-4: charge 40
  11. 11commit offset 4
  12. 12pay-5: charge 50
  13. 13commit offset 5

Charged 180 · should be 150

Nothing is lost, but pay-3 is processed twice after the restart: charged 180 instead of 150.

Consumer

Step 2 of 4

  1. 1

    At-most-once

    Save progress first. A crash loses whatever was in flight, but nothing repeats.

    OK for metrics; never for money.

  2. 2

    At-least-once

    Do the work, then save progress. A crash in between means the message comes again. This is the default for most queues.

    Duplicates are guaranteed, eventually.

  3. 3

    Effectively once

    Give each message an id and store processed ids in the same transaction as the work. Repeats become no-ops.

    INSERT … ON CONFLICT (message_id) DO NOTHING

  4. 4

    Order is per key, not global

    Kafka keeps order within a partition. Send all events for one account to the same partition by keying on the account id.

    Retries can reorder; version numbers help.

In production

Tools and technologies

Queues give you at-least-once; consumers make it safe.

Not tied to one language — use these names when you search docs or design a system.