ЁЯПл The SchoolтА║ЁЯМР Distributed SystemsтА║ЁЯОп рдзрдбрд╛ 11 тАФ Exactly-once рдЪреЗ рдорд┐рдердХ: at-least-once + idempotency
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯОп рдзрдбрд╛ 11 тАФ Exactly-once рдЪреЗ рдорд┐рдердХ: at-least-once + idempotency

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 11 ┬╖ рдорд╛рдЧреЗ: lesson-10-locks-leases ┬╖ рдкреБрдвреЗ: lesson-12-backpressure


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ10, рдЖрдгрд┐ delivery guarantees: at-most-once, at-least-once, рдЖрдгрд┐ systems рдЬреНрдпрд╛рдЪреА рдЬрд╛рд╣рд┐рд░рд╛рдд рдХрд░рддрд╛рдд рддреЗ "exactly-once". Network рдкрд▓реАрдХрдбреЗ message рдиреЗрдордХрд╛ рдПрдХрджрд╛рдЪ рдкреЛрд╣реЛрдЪрд╡рддрд╛ рдпреЗрдд рдирд╛рд╣реА тАФ рдкрдг рддреНрдпрд╛рдЪрд╛ рдкрд░рд┐рдгрд╛рдо рдПрдХрджрд╛рдЪ рдШрдбреВ рд╢рдХрддреЛ: **at-least-once delivery

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдореБрдЦреНрдп office рдлреА рдкрд░рддрд╛рд╡рд╛ рдирд┐рд░реЛрдкреНрдпрд╛рдорд╛рд░реНрдлрдд рдкрд╛рдард╡рддреЗ: рдХрддрд░рд┐рдирд╛рд▓рд╛ рдкреИрд╕реЗ рджреНрдпрд╛, рджреАрдкрд┐рдХрд╛рд▓рд╛ рдкреИрд╕реЗ рджреНрдпрд╛, рдРрд╢реНрд╡рд░реНрдпрд╛рд▓рд╛ рдкреИрд╕реЗ рджреНрдпрд╛. ЁЯТ╕

рдирд┐рд░реЛрдкреНрдпрд╛ "рджреАрдкрд┐рдХрд╛рд▓рд╛ рдкреИрд╕реЗ рджреНрдпрд╛" рдкреЛрд╣реЛрдЪрд╡рддреЛ. рджреАрдкрд┐рдХрд╛рдЪреА рд╢рд╛рдЦрд╛ рддрд┐рд▓рд╛ рдкреИрд╕реЗ рджреЗрддреЗ. рд╢рд╛рдЦрд╛ рдкрд░рдд "рдЭрд╛рд▓реЗ" рдЕрд╕реЗ рдХрд│рд╡рддреЗ тАФ рдЖрдгрд┐ рддреА рдЪрд┐рдареНрдареА рд╣рд░рд╡рддреЗ (рдзрдбрд╛ 01). рдореБрдЦреНрдп office рд▓рд╛ рдХрд╛рд╣реАрдЪ рдХрд│рдд рдирд╛рд╣реА. рдореНрд╣рдгреВрди рддреЗ "рджреАрдкрд┐рдХрд╛рд▓рд╛ рдкреИрд╕реЗ рджреНрдпрд╛" рдкреБрдиреНрд╣рд╛ рдкрд╛рдард╡рддреЗ. рджреАрдкрд┐рдХрд╛рд▓рд╛ рджреЛрдирджрд╛ рдкреИрд╕реЗ рдорд┐рд│рддрд╛рдд. ЁЯШм

рдореБрдЦреНрдп office рдиреЗ рддреЗ рдлрдХреНрдд рдПрдХрджрд╛рдЪ рдкрд╛рдард╡рд▓реЗ, рдЖрдгрд┐ рдкреБрдиреНрд╣рд╛ рдХрдзреАрдЪ рдирд╛рд╣реА, рддрд░? рдордЧ рдкрд╣рд┐рд▓реА рдЪрд┐рдареНрдареАрдЪ рд╣рд░рд╡рд▓реА рддрд░ рджреАрдкрд┐рдХрд╛рд▓рд╛ рд╢реВрдиреНрдп рд╡реЗрд│рд╛ рдкреИрд╕реЗ рдорд┐рд│рддрд╛рдд. рддреЗрд╣реА рд╡рд╛рдИрдЯрдЪ.

рдореНрд╣рдгреВрди рд╢рд╛рдЦрд╛ рдХрд╛рд╣реАрддрд░реА рд╣реБрд╢рд╛рд░реАрдЪреЗ рдХрд░рддрд╛рдд. рдкреНрд░рддреНрдпреЗрдХ рдЖрджреЗрд╢рд╛рд▓рд╛ рдПрдХ рдирд╛рд╡ рдЕрд╕рддреЗ: pay-dipika. рд╢рд╛рдЦрд╛ рдЖрдзреАрдЪ рдХреЗрд▓реЗрд▓реНрдпрд╛ рдирд╛рд╡рд╛рдВрдЪреА рдпрд╛рджреА рдареЗрд╡рддреЗ. ЁЯУЛ "pay-dipika" рджреБрд╕рд▒реНрдпрд╛рдВрджрд╛ рдЖрд▓рд╛ рдХреА рдХрд╛рд░рдХреВрди рдпрд╛рджреА рддрдкрд╛рд╕рддреЗ, рддреЗ рдирд╛рд╡ рддрд┐рдереЗ рдкрд╛рд╣рддреЗ, рдЖрдгрд┐ рдХрд╛рд╣реАрдЪ рдХрд░рдд рдирд╛рд╣реА тАФ рдкрдг рддрд░реАрд╣реА "рдЭрд╛рд▓реЗ" рдЕрд╕реЗ рд╕рд╛рдВрдЧрддреЗ.

рд╣рд╡реЗ рддрд┐рддрдХреНрдпрд╛ рд╡реЗрд│рд╛ рдкрд╛рдард╡рд╛. рдкреИрд╕реЗ рдПрдХрджрд╛рдЪ рдЬрд╛рддрд╛рдд.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    q["ЁЯУм queue<br/>at-least-once: may deliver again"] -->|"pay-katrina, pay-dipika,<br/>pay-aishwarya, pay-dipika again"| c1["plain consumer"]
    q -->|"same four deliveries"| c2["idempotent consumer<br/>checks a list of done keys"]
    c1 --> e1["4 effects<br/>Dipika paid twice"]
    c2 --> e2["3 effects<br/>every payment once"]

ЁЯЧ║я╕П рд░реЗрдЦрд╛рдЯрд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/distributed-systems/lesson-diagrams.html#l11

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг retries рд╕рдЧрд│реАрдХрдбреЗ рдЕрд╕рддрд╛рдд: clients, load balancers, SDKs, queues, рдЖрдгрд┐ timeout рдирдВрддрд░ рддреБрдордЪрд╛ рд╕реНрд╡рддрдГрдЪрд╛ code. рдкреНрд░рддреНрдпреЗрдХ рдЬрдг рд╕реНрд╡рддрдГрдЪреНрдпрд╛ рдЬрд╛рдЧреА рдмрд░реЛрдмрд░ рдЕрд╕рддреЛ, рдЖрдгрд┐ рд╕рдЧрд│реЗ рдорд┐рд│реВрди duplicates рдкреЛрд╣реЛрдЪрд╡рддрд╛рдд. Idempotent рдирд╕рд▓реЗрд▓реЗ payment, email рдХрд┐рдВрд╡рд╛ stock count production рдордзреНрдпреЗ рджреЛрдирджрд╛ рдШрдбрдгрд╛рд░рдЪ; рдкреНрд░рд╢реНрди рдлрдХреНрдд рдХрд┐рддреА рд╡реЗрд│рд╛ рдЗрддрдХрд╛рдЪ. Product рдЪреНрдпрд╛ page рд╡рд░ рдЫрд╛рдкрд▓реЗрд▓реНрдпрд╛ "Exactly once" рдЪрд╛ рдЕрд░реНрде рд╕рд╣рд╕рд╛ "рдЖрдордЪреНрдпрд╛ system рдЪреНрдпрд╛ рдЖрдд" рдЕрд╕рд╛ рдЕрд╕рддреЛ тАФ рддреНрдпрд╛ system рдЪреА рдХрдб рдореНрд╣рдгрдЬреЗрдЪ рдЬрд┐рдереЗ рддреБрдордЪреА idempotency key рдЬрд╛рддреЗ.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

dist/sim.py рдордзреАрд▓ deliver(messages, redeliver, idempotent) рдЖрдзреА messages рдЖрдгрд┐ рдордЧ redeliveries process рдХрд░рддреЗ (at-least-once). рд╕рд╛рдзрд╛ consumer рдкреНрд░рддреНрдпреЗрдХ message рд▓рд╛рдЧреВ рдХрд░рддреЛ. Idempotent consumer message keys рдЪрд╛ рдПрдХ seen set рдареЗрд╡рддреЛ рдЖрдгрд┐ рдЖрдзреАрдЪ рд▓рд╛рдЧреВ рдХреЗрд▓реЗрд▓реА рдХреЛрдгрддреАрд╣реА key рд╡рдЧрд│рддреЛ. рддреЗ рдкрд░рд┐рдгрд╛рдорд╛рдВрдЪреА рдпрд╛рджреА рдкрд░рдд рдХрд░рддреЗ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 dist/demo.py exactlyonce
python3 - <<'EOF'
import sys; sys.path.insert(0, "dist"); from sim import deliver
msgs = ["pay-katrina", "pay-dipika", "pay-aishwarya"]
again = ["pay-dipika", "pay-dipika", "pay-katrina"]            # a bad night: three redeliveries
for idem in (False, True):
    eff = deliver(msgs, again, idem)
    print(f"{'idempotent' if idem else 'plain     '} тЖТ {len(eff)} payments: {eff}")
print("at-most-once (never redeliver) and a lost message тЖТ", deliver(["pay-katrina", "pay-aishwarya"], [], True))
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

exactlyonce рд╣реЗ print рдХрд░рддреЗ:

тФАтФА the queue redelivers pay-dipika (at-least-once) ┬╖ plain consumer      тЖТ effects ['pay-katrina', 'pay-dipika', 'pay-aishwarya', 'pay-dipika']
тФАтФА the queue redelivers pay-dipika (at-least-once) ┬╖ idempotent consumer  тЖТ effects ['pay-katrina', 'pay-dipika', 'pay-aishwarya']

рддреБрдордЪрд╛ snippet рд╣реЗ print рдХрд░рддреЛ:

plain      тЖТ 6 payments: ['pay-katrina', 'pay-dipika', 'pay-aishwarya', 'pay-dipika', 'pay-dipika', 'pay-katrina']
idempotent тЖТ 3 payments: ['pay-katrina', 'pay-dipika', 'pay-aishwarya']
at-most-once (never redeliver) and a lost message тЖТ ['pay-katrina', 'pay-aishwarya']

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рддреАрди redeliveries рдореБрд│реЗ рд╕рд╛рдзреНрдпрд╛ consumer рд╕рд╛рдареА 3 payments рдЪреЗ 6 рдЭрд╛рд▓реЗ, рдЖрдгрд┐ idempotent consumer рд╕рд╛рдареА рдХрд╛рд╣реАрдЪ рдмрджрд▓рд▓реЗ рдирд╛рд╣реА. рд╢реЗрд╡рдЯрдЪреА рдУрд│ рджреБрд╕рд░реА рдмрд╛рдЬреВ рджрд╛рдЦрд╡рддреЗ: at-most-once рдордзреНрдпреЗ рд╣рд░рд╡рд▓реЗрд▓рд╛ pay-dipika рд╕рд░рд│ рдирд╛рд╣реАрд╕рд╛ рд╣реЛрддреЛ тАФ duplicates рдирд╛рд╣реАрдд, рдЖрдгрд┐ payment рд╣реА рдирд╛рд╣реА. рдпрд╛ рджреЛрдШрд╛рдВрдкреИрдХреА at-least-once + key рд╣рд╛рдЪ рдЕрд╕рд╛ рдЖрд╣реЗ рдЬреЛ рддреБрдореНрд╣реА рджреБрд░реБрд╕реНрдд рдХрд░реВ рд╢рдХрддрд╛.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

SQL рдордзреНрдпреЗ idempotent consumer тАФ key рдиреЛрдВрджрд╡рдгреЗ рдЖрдгрд┐ рдкрд░рд┐рдгрд╛рдо рд▓рд╛рдЧреВ рдХрд░рдгреЗ рдПрдХрд╛рдЪ transaction рдордзреНрдпреЗ рдХрд░рд╛. On a real account (PostgreSQL):

BEGIN;
INSERT INTO processed_messages (message_id) VALUES ('pay-dipika')
    ON CONFLICT (message_id) DO NOTHING;            -- 0 rows inserted тЖТ already done: ROLLBACK and ack
UPDATE fees SET refunded = true WHERE student = 'dipika';
COMMIT;                                             -- then delete the message from the queue

Amazon SQS тАФ standard queues at-least-once рдЕрд╕рддрд╛рдд (рдЖрдгрд┐ рдХреНрд░рдо рдмрджрд▓реВрди рджреЗрдК рд╢рдХрддрд╛рдд). рдЬреЛ message рддреНрдпрд╛рдЪреНрдпрд╛ visibility timeout рдордзреНрдпреЗ delete рд╣реЛрдд рдирд╛рд╣реА рддреЛ рдкреБрдиреНрд╣рд╛ рджрд┐рд▓рд╛ рдЬрд╛рддреЛ. FIFO queues 5-minute рдЪреНрдпрд╛ window рдордзреНрдпреЗ рддреНрдпрд╛рдЪ MessageDeduplicationId рд╕рд╣ рдкрд╛рдард╡рд▓реЗрд▓рд╛ message рдЯрд╛рдХреВрди рджреЗрддрд╛рдд тАФ рдпрд╛рдореБрд│реЗ duplicate sends рдерд╛рдВрдмрддрд╛рдд, рдкрдг рддреБрдордЪрд╛ consumer message delete рдХрд░рдгреНрдпрд╛рдЖрдзреА crash рдЭрд╛рд▓рд╛ рддрд░ рддреЛ рдЕрдЬреВрдирд╣реА message рджреЛрдирджрд╛ process рдХрд░реВ рд╢рдХрддреЛ:

aws sqs send-message --queue-url https://sqs.ap-south-1.amazonaws.com/111122223333/refunds.fifo \
    --message-body '{"student":"dipika","amount":600}' \
    --message-group-id dipika --message-deduplication-id pay-dipika

Kafka тАФ рд╢рдмреНрджрд╛рдВрдд: idempotent producer (enable.idempotence=true, Kafka 3.0 рдкрд╛рд╕реВрди default, acks=all рд╕рд╣) producer рдЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ retries рдореБрд│реЗ рд╣реЛрдгрд╛рд░реЗ duplicates рдерд╛рдВрдмрд╡рддреЛ. Transactions (producer рд╡рд░ transactional.id; consumers рд╡рд░ isolation.level=read_committed) Kafka рдЪреНрдпрд╛ рдЖрдд read-process-write рд╕рд░реНрд╡-рдХрд┐рдВрд╡рд╛-рдХрд╛рд╣реАрдЪ рдирд╛рд╣реА рдЕрд╕реЗ рдХрд░рддрд╛рдд, consumer offsets рд╕рдХрдЯ. Kafka Streams рд╣реЗ processing.guarantee=exactly_once_v2 рдиреЗ рдЪрд╛рд▓реВ рдХрд░рддреЗ. Database рдордзреНрдпреЗ рд▓рд┐рд╣рд┐рдгрд╛рд▒реНрдпрд╛ consumer рд▓рд╛ рддрд┐рдереЗ рдЕрдЬреВрдирд╣реА key рдХрд┐рдВрд╡рд╛ upsert рд▓рд╛рдЧрддреЛ.

HTTP APIs тАФ POST requests рд╡рд░ Idempotency-Key header рд╕реНрд╡реАрдХрд╛рд░рд╛ (Stripe рдЕрд╕реЗ рдХрд░рддреЗ) рдЖрдгрд┐ рдкреБрдиреНрд╣рд╛ рдЖрд▓реЗрд▓реНрдпрд╛ key рд╕рд╛рдареА рдЬрддрди рдХреЗрд▓реЗрд▓рд╛ response рдкрд░рдд рдХрд░рд╛.

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдкреИрд╕реЗ рд╣рд▓рд╡рдгрд╛рд░рд╛, messages рдкрд╛рдард╡рдгрд╛рд░рд╛ рдХрд┐рдВрд╡рд╛ counts рдмрджрд▓рдгрд╛рд░рд╛ рдкреНрд░рддреНрдпреЗрдХ consumer рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ POST endpoint рдпрд╛рджреАрдд рд▓рд┐рд╣рд╛. рдкреНрд░рддреНрдпреЗрдХрд╛рд╕рд╛рдареА рддреНрдпрд╛рдЪреА idempotency key рдЖрдгрд┐ рддреА key рдХреБрдареЗ рд╕рд╛рдард╡рд▓реА рдЬрд╛рддреЗ рддреЗ рд╕рд╛рдВрдЧрд╛. "Queue exactly-once рдХрд░рддреЗ" рд╣реЗ рдЙрддреНрддрд░ рдирд╡реНрд╣реЗ.

тПня╕П рдкреБрдвреЗ

рд╢реЗрд╡рдЯрдЪреА рдЕрдбрдЪрдг: рдЦреВрдк рдЬрд╛рд╕реНрдд рдХрд╛рдо. Retries, bursts рдЖрдгрд┐ рд╣рд│реВ рдкреНрд░рддреА рд╕рд╛рдЪрдд рдЬрд╛рддрд╛рдд, рдЖрдгрд┐ рд╕рдЧрд│реЗ рд╕реНрд╡реАрдХрд╛рд░рдгрд╛рд░реА system рд╕рдЧрд│реНрдпрд╛рдВрд╕рд╛рдареАрдЪ рд╣рд│реВ рд╣реЛрддреЗ. рдкреБрдвреЗ: backpressure тАФ рдЖрдгрд┐ рд╕рдВрдкреВрд░реНрдг рдирдХрд╛рд╢рд╛.

git checkout lesson-12-backpressure

ЁЯОп Lesson 11 тАФ The exactly-once myth: at-least-once + idempotency

ЁЯУН You are here: Lesson 11 of 12 ┬╖ Previous: lesson-10-locks-leases ┬╖ Next: lesson-12-backpressure


ЁЯУж What's in this branch

Lessons 01тАУ10, plus delivery guarantees: at-most-once, at-least-once, and the "exactly-once" that systems advertise. Across a network, a message cannot be delivered exactly once тАФ but its effect can happen once: **at-least-once delivery

ЁЯзТ Explain like I'm 5

The head office sends fee refunds by messenger: pay Katrina, pay Dipika, pay Aishwarya. ЁЯТ╕

The messenger delivers "pay Dipika". Dipika's branch pays her. The branch sends back "done" тАФ and that note is lost (lesson 01). The head office hears nothing. So it sends "pay Dipika" again. Dipika is paid twice. ЁЯШм

Could the head office send it only once, and never again? Then if the first note was lost, Dipika is paid zero times. Also bad.

So the branches do something smarter. Each order has a name: pay-dipika. The branch keeps a list of names it has already done. ЁЯУЛ When "pay-dipika" comes a second time, the clerk checks the list, sees it, and does nothing тАФ but still says "done".

Send as many times as you like. The money moves once.

ЁЯЧ║я╕П Diagram

flowchart LR
    q["ЁЯУм queue<br/>at-least-once: may deliver again"] -->|"pay-katrina, pay-dipika,<br/>pay-aishwarya, pay-dipika again"| c1["plain consumer"]
    q -->|"same four deliveries"| c2["idempotent consumer<br/>checks a list of done keys"]
    c1 --> e1["4 effects<br/>Dipika paid twice"]
    c2 --> e2["3 effects<br/>every payment once"]

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/distributed-systems/lesson-diagrams.html#l11

тЭУ What

ЁЯдФ Why

Because retries are everywhere: clients, load balancers, SDKs, queues, and your own code after a timeout. Each one is correct on its own, and together they deliver duplicates. A payment, an email or a stock count that is not idempotent will happen twice in production; the only question is how often. "Exactly once" printed on a product's page usually means "within our system" тАФ the edge of that system is where your idempotency key goes.

ЁЯФз How (in this repo)

deliver(messages, redeliver, idempotent) in dist/sim.py processes the messages and then the redeliveries (at-least-once). A plain consumer applies every one. An idempotent consumer keeps a seen set of message keys and skips any key it has already applied. It returns the list of effects.

ЁЯзк Try it

python3 dist/demo.py exactlyonce
python3 - <<'EOF'
import sys; sys.path.insert(0, "dist"); from sim import deliver
msgs = ["pay-katrina", "pay-dipika", "pay-aishwarya"]
again = ["pay-dipika", "pay-dipika", "pay-katrina"]            # a bad night: three redeliveries
for idem in (False, True):
    eff = deliver(msgs, again, idem)
    print(f"{'idempotent' if idem else 'plain     '} тЖТ {len(eff)} payments: {eff}")
print("at-most-once (never redeliver) and a lost message тЖТ", deliver(["pay-katrina", "pay-aishwarya"], [], True))
EOF

тЬЕ Verify тАФ what you should see

exactlyonce prints:

тФАтФА the queue redelivers pay-dipika (at-least-once) ┬╖ plain consumer      тЖТ effects ['pay-katrina', 'pay-dipika', 'pay-aishwarya', 'pay-dipika']
тФАтФА the queue redelivers pay-dipika (at-least-once) ┬╖ idempotent consumer  тЖТ effects ['pay-katrina', 'pay-dipika', 'pay-aishwarya']

Your snippet prints:

plain      тЖТ 6 payments: ['pay-katrina', 'pay-dipika', 'pay-aishwarya', 'pay-dipika', 'pay-dipika', 'pay-katrina']
idempotent тЖТ 3 payments: ['pay-katrina', 'pay-dipika', 'pay-aishwarya']
at-most-once (never redeliver) and a lost message тЖТ ['pay-katrina', 'pay-aishwarya']

ЁЯПБ What you just proved

Three redeliveries turned 3 payments into 6 for the plain consumer, and changed nothing for the idempotent one. The last line is the other side: with at-most-once, a lost pay-dipika is simply gone тАФ no duplicates, and no payment. Of the two, at-least-once + a key is the one you can fix.

тЪая╕П Common mistakes

ЁЯПн In production

An idempotent consumer in SQL тАФ record the key and apply the effect in one transaction. On a real account (PostgreSQL):

BEGIN;
INSERT INTO processed_messages (message_id) VALUES ('pay-dipika')
    ON CONFLICT (message_id) DO NOTHING;            -- 0 rows inserted тЖТ already done: ROLLBACK and ack
UPDATE fees SET refunded = true WHERE student = 'dipika';
COMMIT;                                             -- then delete the message from the queue

Amazon SQS тАФ standard queues are at-least-once (and may deliver out of order). A message that is not deleted within its visibility timeout is delivered again. FIFO queues drop a message with the same MessageDeduplicationId sent within a 5-minute window тАФ this stops duplicate sends, but your consumer can still process a message twice if it crashes before deleting it:

aws sqs send-message --queue-url https://sqs.ap-south-1.amazonaws.com/111122223333/refunds.fifo \
    --message-body '{"student":"dipika","amount":600}' \
    --message-group-id dipika --message-deduplication-id pay-dipika

Kafka тАФ in words: the idempotent producer (enable.idempotence=true, the default since Kafka 3.0, with acks=all) stops duplicates caused by the producer's own retries. Transactions (a transactional.id on the producer; isolation.level=read_committed on consumers) make read-process-write inside Kafka all-or-nothing, including the consumer offsets. Kafka Streams turns this on with processing.guarantee=exactly_once_v2. A consumer that writes to a database still needs a key or an upsert there.

HTTP APIs тАФ accept an Idempotency-Key header on POST requests (Stripe does this) and return the saved response for a repeated key.

ЁЯПн Why this matters in production: list every consumer and every POST endpoint that moves money, sends messages or changes counts. For each, name its idempotency key and where the key is stored. "The queue does exactly-once" is not an answer.

тПня╕П Next

The last problem: too much work. Retries, bursts and slow copies pile up, and a system that accepts everything slows down for everyone. Next: backpressure тАФ and the whole map.

git checkout lesson-12-backpressure
тЖР Previouslocks leasesNext тЖТbackpressure

This page is the lesson's README from the lesson-11-exactly-once branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.