Start HereOrdered learning track

Delivery Semantics: At-Most-Once, At-Least-Once, Effectively-Once

Learn AWS Application and Database - Part 014

Delivery semantics di AWS: at-most-once, at-least-once, exactly-once claim, effectively-once application design, idempotency, deduplication, replay, DLQ, dan operational safety.

9 min read1659 words
PrevNext
Lesson 1496 lesson track01–17 Start Here
#aws#application-integration#delivery-semantics#sqs+6 more

Part 014 — Delivery Semantics: At-Most-Once, At-Least-Once, Effectively-Once

Tujuan bagian ini: memahami apa arti delivery semantics di production, terutama ketika memakai SQS, SNS, EventBridge, Lambda/worker, database transaction, outbox, dan external side effects. Target akhirnya bukan menghafal istilah “exactly once”, tetapi mampu membangun sistem yang benar walaupun pesan duplicate, retry terjadi, ack hilang, event telat, dan replay dijalankan.

Distributed system tidak gagal dengan cara yang sopan.

Ia gagal di antara dua baris kode:

1. Efek bisnis sudah terjadi.
2. Acknowledgement belum tercatat.

Ia gagal ketika:

- database commit berhasil, response ke client timeout,
- worker memproses pesan, lalu crash sebelum delete message,
- EventBridge gagal deliver target dan retry,
- SNS berhasil ke satu subscriber tetapi gagal ke subscriber lain,
- SQS visibility timeout habis saat consumer masih bekerja,
- Lambda berhasil menjalankan sebagian batch lalu error,
- DLQ di-redrive dan side effect lama terjadi lagi,
- operator replay event untuk recovery tetapi consumer tidak replay-safe.

Delivery semantics bukan teori. Ia menentukan apakah uang tertagih dua kali, email hukum terkirim dua kali, case dieskalasi ganda, inventory negatif, atau audit trail rusak.


1. Tiga Istilah yang Sering Disalahpahami

At-Most-Once

Pesan diproses nol atau satu kali.
Tidak ada duplicate, tetapi pesan boleh hilang.

Cocok untuk:

  • metric non-kritis,
  • telemetry sampling,
  • update ephemeral,
  • best-effort notification yang boleh hilang.

Tidak cocok untuk:

  • pembayaran,
  • order,
  • lifecycle case,
  • legal notice,
  • inventory reservation,
  • audit event.

At-Least-Once

Pesan diproses satu kali atau lebih.
Tidak boleh hilang secara normal, tetapi duplicate mungkin terjadi.

Ini default mental model untuk banyak sistem messaging durable.

Konsekuensi:

Consumer harus idempotent.
Efek samping harus aman terhadap duplicate.
Ordering tidak boleh diasumsikan kecuali service/konfigurasi menjaminnya dalam scope tertentu.

Exactly-Once

Pesan diproses tepat satu kali.

Istilah ini berbahaya karena sering hanya berlaku pada scope sempit:

  • producer deduplication,
  • broker internal write,
  • stream processing transaction,
  • dedup window tertentu,
  • partition tertentu,
  • bukan seluruh business side effect end-to-end.

Dalam sistem aplikasi umum, target yang realistis adalah:

Effectively-once business effect.

2. Effectively-Once: Target yang Benar

Effectively-once berarti:

Transport boleh mengirim duplicate.
Worker boleh retry.
Ack boleh hilang.
Replay boleh terjadi.
Tetapi efek bisnis final terjadi satu kali secara observable.

Caranya bukan berharap broker sempurna.

Caranya:

- idempotency key
- conditional write / unique constraint
- transactional state change
- processed-message/inbox record
- ack setelah commit
- monotonic version
- replay-safe side effect
- reconciliation job

Diagram:

Targetnya bukan “pesan hanya datang sekali”. Targetnya “efek bisnis tidak berganda”.


3. Receive-Process-Ack Gap

Semua queue consumer punya gap ini:

Kalau worker tidak idempotent, efek terjadi dua kali.

Ini alasan utama mengapa at-least-once delivery harus dipasangkan dengan idempotent consumer.


4. SQS Standard Queue Semantics

SQS Standard Queue dirancang untuk throughput tinggi dan at-least-once delivery. Dalam arsitektur terdistribusi, duplicate bisa terjadi, dan ordering tidak boleh dianggap strict global.

Mental model:

SQS Standard:
  - durable queue
  - highly scalable
  - at-least-once delivery
  - duplicate possible
  - best-effort ordering

Implikasi desain:

Jangan menaruh correctness pada asumsi message hanya datang sekali.
Jangan menaruh correctness pada asumsi ordering global.
Jangan menghapus pesan sebelum efek bisnis commit.

Consumer harus:

- idempotent
- punya visibility timeout sesuai processing time
- punya DLQ untuk poison message
- punya retry limit
- punya metric ApproximateAgeOfOldestMessage/backlog

5. SQS FIFO Queue Semantics

SQS FIFO Queue memberi ordering dalam message group dan deduplication dalam scope tertentu.

Mental model:

SQS FIFO:
  - ordering per MessageGroupId
  - deduplication via MessageDeduplicationId atau content-based dedup
  - dedup window terbatas
  - throughput dipengaruhi group cardinality

FIFO membantu, tetapi tidak menghapus kebutuhan idempotency aplikasi.

Kenapa?

- deduplication window terbatas
- consumer bisa crash setelah efek commit sebelum delete
- side effect eksternal bisa retry di luar FIFO
- redrive dari DLQ bisa mengulang pesan lama
- business duplicate bisa punya payload berbeda tetapi efek sama

Rule:

FIFO membantu ordering dan producer-side duplicate.
Effectively-once tetap tugas aplikasi.

6. Visibility Timeout Bukan Lock Permanen

Visibility timeout adalah masa ketika pesan yang diterima consumer disembunyikan dari consumer lain.

Jika processing melebihi timeout:

Pesan bisa muncul lagi.
Consumer lain bisa memprosesnya bersamaan.

Ini failure yang sering diremehkan.

Tanpa idempotency/lock/conditional write, duplicate concurrent effect bisa terjadi.

Praktik:

- set visibility timeout lebih besar dari p99 processing time
- extend visibility timeout untuk job panjang
- gunakan heartbeat/progress bila perlu
- batasi concurrency per key jika ordering penting
- gunakan conditional write untuk mencegah duplicate commit

7. EventBridge Delivery and Retry Semantics

EventBridge adalah event bus serverless untuk menghubungkan komponen melalui event. Target delivery bisa gagal. EventBridge mendukung retry policy dan DLQ untuk event yang tidak bisa dikirim ke target.

Mental model:

EventBridge tidak berarti target pasti sukses.
EventBridge berarti event bisa dirutekan dan delivery dicoba sesuai policy.
Jika retry habis dan tidak ada DLQ, event bisa drop untuk target tersebut.

Implikasi:

Setiap target penting harus punya failure strategy.
Gunakan DLQ untuk target delivery failure.
Consumer tetap harus idempotent.
Replay/archive harus diperlakukan sebagai duplicate/reprocessing.

EventBridge cocok untuk routing fact, tetapi bukan pengganti state machine untuk proses kritis yang membutuhkan progress eksplisit.


8. SNS Delivery and DLQ Semantics

SNS adalah pub/sub fanout. Setiap subscription punya delivery behavior sendiri.

Risiko:

Publisher berhasil publish ke topic.
Subscriber A menerima.
Subscriber B gagal.
Subscriber C throttled.

Jika konsekuensi penting, jangan hanya berpikir “SNS publish sukses”. Pikirkan per subscription:

- retry policy
- DLQ
- endpoint health
- backpressure
- duplicate delivery
- subscription filter correctness

Pola sehat untuk workload penting:

SNS Topic -> SQS Queue per consumer -> Worker

Dengan queue per consumer, masing-masing consumer punya retry, DLQ, backlog, dan redrive boundary sendiri.


9. Lambda Batch Semantics dengan Queue/Stream

Ketika Lambda memproses batch dari SQS/stream, failure satu record bisa memengaruhi batch.

Masalah umum:

Batch berisi 10 pesan.
9 berhasil diproses.
1 gagal.
Function mengembalikan error.
Seluruh batch bisa diproses ulang jika partial batch response tidak dipakai/dikonfigurasi.

Konsekuensi:

9 pesan yang sudah berhasil harus idempotent terhadap retry.

Praktik:

- gunakan partial batch failure response bila cocok
- idempotency tetap wajib
- jangan proses side effect irreversible tanpa record effect key
- log per record, bukan hanya per batch
- metric failure per event type/reason

Batch meningkatkan throughput, tetapi memperbesar blast radius duplicate jika desain consumer lemah.


10. Database Transaction sebagai Boundary Efek

Efek bisnis yang penting harus diputuskan di boundary transactional.

Contoh dengan relational DB:

BEGIN;

INSERT INTO processed_messages(message_id, consumer_name, processed_at)
VALUES (:message_id, :consumer_name, now())
ON CONFLICT DO NOTHING;

-- hanya lanjut jika insert berhasil
UPDATE cases
SET status = 'ESCALATED', version = version + 1
WHERE case_id = :case_id
  AND status = 'SUBMITTED';

COMMIT;

Lebih baik gunakan constraint:

CREATE UNIQUE INDEX uq_processed_message
ON processed_messages(consumer_name, message_id);

Dengan DynamoDB:

PutItem processedKey with condition attribute_not_exists(pk)
TransactWriteItems:
  - condition/put processed key
  - update domain item conditionally

Kuncinya:

Cek duplicate dan apply effect harus atomic atau setidaknya race-safe.

11. Idempotency Key: Scope Lebih Penting dari Bentuk

Idempotency key buruk:

caseId

Kenapa buruk?

Karena satu case bisa punya banyak command/event berbeda.

Lebih baik:

command:approve-case:case-C-1001:actor-user-912:client-request-abc
consumer:notification:case-approved:C-1001:version-18:template-v3
projection:worklist:case-approved:C-1001:version-18

Idempotency key harus menjawab:

Efek apa yang tidak boleh terjadi dua kali?
Dalam scope siapa?
Untuk versi/fakta mana?

Jika scope terlalu luas, efek valid akan ditolak. Jika scope terlalu sempit, duplicate lolos.


12. Deduplication Tidak Sama dengan Idempotency

Deduplication:

Mendeteksi input sama dalam window/scope tertentu dan membuang duplicate.

Idempotency:

Menjamin efek akhir sama walaupun operasi dipanggil berulang.

Contoh:

Dua pesan berbeda:
  evt-1 CaseApproved case=C-1001 version=18
  evt-2 CaseApproved case=C-1001 version=18

Message ID berbeda, tetapi business effect sama.

Dedup by messageId tidak cukup. Kamu butuh effect key:

email-case-approved:C-1001:v18:recipient-x

Sebaliknya, pesan sama bisa diproses ulang untuk rebuild projection. Dalam mode rebuild, kamu mungkin ingin re-apply secara deterministic.


13. Ordering: Global Ordering Hampir Selalu Salah Asumsi

Pertanyaan yang benar bukan:

Apakah sistem menjamin ordering?

Pertanyaan yang benar:

Ordering untuk key apa?
Dalam partition/message group apa?
Untuk producer mana?
Dalam window berapa?
Apa yang terjadi saat retry/redrive/replay?

Contoh event:

CaseSubmitted v1
CaseAssigned v2
CaseApproved v3

Projection menerima:

CaseApproved v3 dulu
CaseAssigned v2 kemudian

Projection harus punya guard:

if incomingVersion <= currentVersion:
    ignore or route to reconciliation

Jangan mengandalkan waktu kedatangan sebagai urutan domain.

Gunakan:

- aggregate version
- sequence number per aggregate
- occurredAt untuk informasi, bukan satu-satunya ordering guard
- conditional update currentVersion < incomingVersion

14. Monotonic State Machine

Untuk lifecycle, buat transition monotonic.

Consumer projection bisa menjaga state dengan rule:

- event version lebih lama: ignore
- transition tidak dikenal: quarantine
- transition mundur: reject/reconcile
- duplicate version: idempotent no-op

Contoh pseudo-code:

void apply(CaseApproved event) {
    readModel.updateIf(
        key = event.caseId(),
        condition = current.version < event.caseVersion(),
        update = setStatus("APPROVED", event.caseVersion())
    );
}

15. Ack Setelah Commit

Rule dasar consumer:

Ack/delete message hanya setelah efek yang diperlukan berhasil commit.

Buruk:

receive message
ack/delete message
process database update

Jika database update gagal setelah ack, pesan hilang.

Lebih baik:

receive message
process database update transactionally
commit
ack/delete message

Ini membuka kemungkinan duplicate jika crash setelah commit sebelum ack. Itulah alasan idempotency wajib.

Trade-off:

Ack before commit -> risiko lost effect.
Ack after commit  -> risiko duplicate delivery.

Untuk efek penting, pilih duplicate yang bisa dibuat aman, bukan kehilangan fakta.


16. External Side Effect Paling Sulit

Database bisa diberi unique constraint. External system belum tentu.

Contoh side effect:

- charge payment
- send legal notice
- send email/SMS
- create ticket in third-party system
- call regulator gateway
- issue certificate

Strategi:

1. Gunakan idempotency key di external API jika tersedia.
2. Simpan local effect record sebelum/sesudah call dengan state machine.
3. Jangan replay otomatis side effect irreversible tanpa guard.
4. Pisahkan “decision event” dari “side effect command”.
5. Buat reconciliation terhadap external system.

State machine lokal:

Status Unknown penting. Timeout setelah request bukan berarti gagal.


17. Ambiguous Commit Problem

Kasus klasik:

Worker memanggil payment provider.
Request timeout.
Tidak tahu apakah payment provider berhasil charge atau tidak.

Jangan langsung retry buta.

Gunakan:

- external idempotency key
- query status by idempotency key/reference
- local effect record status UNKNOWN
- reconciliation worker
- bounded retry

Pseudo-flow:

if localEffect.status == SUCCEEDED:
  no-op
if localEffect.status == UNKNOWN:
  query external by reference
if external says succeeded:
  mark SUCCEEDED
if external says absent and retry budget remains:
  retry with same idempotency key

Ini jauh lebih aman daripada “catch timeout lalu retry dengan request baru”.


18. Replay Safety

Replay adalah bagian dari operasi production:

- rebuild projection
- recover failed consumer
- backfill data
- fix bug
- restore after outage
- redrive DLQ

Replay berbahaya jika consumer mencampur projection update dan irreversible side effect.

Buruk:

on CaseApproved:
  update read model
  send approval email

Saat replay untuk rebuild read model, email terkirim lagi.

Lebih baik pisahkan consumer:

CaseApproved -> WorklistProjectionConsumer
CaseApproved -> NotificationCommandConsumer

Dan notification consumer punya effect key:

notification:case-approved:C-1001:v18:recipient-x

Untuk replay projection, nonaktifkan side-effect consumer atau gunakan replay mode eksplisit.


19. DLQ Bukan Tempat Sampah

DLQ adalah quarantine.

Pesan masuk DLQ karena:

- poison message/schema invalid
- dependency down terlalu lama
- bug consumer
- authorization/config error
- payload melanggar invariant
- external service selalu gagal

DLQ harus punya workflow:

1. Detect.
2. Classify.
3. Fix root cause.
4. Decide: redrive, transform, skip, compensate.
5. Audit decision.

Jangan redrive DLQ massal tanpa:

- idempotency check
- rate limit
- ordering consideration
- downstream capacity check
- replay side effect policy

DLQ yang tidak dimonitor sama dengan data loss tertunda.


20. Poison Message Handling

Poison message adalah pesan yang selalu gagal diproses.

Penyebab:

- schema tidak kompatibel
- required entity tidak ada
- invalid state transition
- data corruption
- consumer bug
- dependency selalu menolak

Consumer tidak boleh retry selamanya dengan kecepatan tinggi.

Strategi:

- bounded retry
- exponential backoff bila relevan
- DLQ setelah threshold
- classify permanent vs transient failure
- include failure reason metadata
- create repair tooling

Pseudo-code:

try {
    process(message);
    ack(message);
} catch (PermanentSchemaException e) {
    sendToQuarantine(message, e);
    ack(message);
} catch (TransientDependencyException e) {
    throw e; // let queue retry / visibility timeout
}

Tidak semua error layak retry.


21. Retry Policy: Retry Bisa Menjadi Serangan ke Sistem Sendiri

Retry tanpa kontrol memperbesar outage.

Risiko:

- dependency sedang down
- semua client retry bersamaan
- queue backlog naik
- worker autoscale agresif
- database connection habis
- DLQ meledak

Praktik:

- exponential backoff
- jitter
- max attempts
- retry budget
- circuit breaker untuk sync call
- backpressure
- idempotency before retry

Rule:

Retry hanya aman jika operasi idempotent dan dependency punya kapasitas menerima retry.

22. Delivery Semantics per AWS Primitive

PrimitiveMental Model DeliveryDesain Aplikasi yang Dibutuhkan
API Gateway requestclient bisa timeout/retryidempotent mutating API
SQS Standardat-least-once, duplicate possibleidempotent consumer, no global ordering assumption
SQS FIFOordering per group, dedup windowidempotency tetap, group cardinality design
SNSpub/sub delivery per subscriptionsubscription DLQ, duplicate-safe consumer
EventBridgeevent routing with retry/DLQ optionstarget idempotency, archive/replay safety
Step Functionsdurable workflow statetask idempotency, retry/catch/timeout design
DynamoDB Streamschange stream from tableconsumer idempotency, ordering scope awareness
Outbox relaypublish retry possibleevent idempotency, relay dedup/marking
External APIunknown after timeoutidempotency token, reconciliation

Tidak ada baris yang mengatakan “tidak perlu idempotency”.


23. Inbox Pattern untuk Consumer

Inbox pattern menyimpan pesan yang sudah diproses oleh consumer.

Relational example:

CREATE TABLE consumer_inbox (
  consumer_name text NOT NULL,
  message_id text NOT NULL,
  effect_key text NOT NULL,
  processed_at timestamptz NOT NULL DEFAULT now(),
  result jsonb,
  PRIMARY KEY (consumer_name, effect_key)
);

Flow:

1. receive message
2. derive effect_key
3. begin transaction
4. insert inbox row; if conflict -> duplicate, no-op
5. apply business effect
6. commit
7. ack/delete message

DynamoDB version:

PK = CONSUMER#notification-service
SK = EFFECT#case-approved#C-1001#v18#recipient-x
ConditionExpression = attribute_not_exists(PK)

Inbox tidak harus disimpan selamanya untuk semua efek. Retensi tergantung replay horizon dan audit requirement.


24. Outbox Relay Idempotency

Relay juga bisa duplicate.

Kasus:

Relay publish event ke EventBridge berhasil.
Relay crash sebelum mark outbox row as published.
Relay restart dan publish event yang sama lagi.

Solusi:

- eventId stabil dari outbox row
- consumer dedupe by eventId/effectKey
- relay mark published best-effort
- monitoring unpublished age

Jangan menganggap outbox relay menjamin event tidak duplicate. Ia menjamin event tidak hilang setelah commit, dengan konsekuensi duplicate possible.


25. Redrive Semantics

Redrive berarti mengirim ulang pesan dari DLQ/source untuk diproses ulang.

Sebelum redrive, tanyakan:

Apakah root cause sudah diperbaiki?
Apakah consumer idempotent?
Apakah pesan masih valid menurut schema/current domain state?
Apakah ordering penting?
Apakah downstream punya kapasitas?
Apakah external side effect aman?
Apakah ada pesan yang harus di-skip/transform?

Redrive yang baik dilakukan bertahap:

- sample kecil
- monitor error/latency/backlog
- rate limit
- pause jika DLQ baru naik
- audit jumlah sukses/gagal/skipped

26. Reconciliation: Safety Net untuk Semua Semantics

Tidak ada desain messaging yang sempurna tanpa reconciliation.

Reconciliation job mencari mismatch antara source of truth dan derived state/effect.

Contoh:

Source of truth: case status APPROVED.
Expected projection: worklist no longer contains case.
Expected notification: approval notification sent once.
Expected task: follow-up task exists if risk HIGH.

Query reconciliation:

Find cases approved in last 24h where:
  - no CaseApproved event published, or
  - worklist still contains case, or
  - notification effect missing, or
  - high-risk follow-up task missing.

Reconciliation mengubah recovery dari “semoga benar” menjadi “bisa dibuktikan dan diperbaiki”.


27. Operational Metrics

Minimum metrics untuk delivery semantics:

queue_visible_messages{queue}
queue_oldest_message_age_seconds{queue}
queue_inflight_messages{queue}
dlq_messages{queue}
consumer_success_total{consumer,eventType}
consumer_failure_total{consumer,eventType,reason}
consumer_duplicate_total{consumer,eventType}
consumer_processing_latency_ms{consumer,eventType}
consumer_visibility_timeout_extension_total{consumer}
outbox_unpublished_count
outbox_oldest_unpublished_age_seconds
eventbridge_failed_invocations{rule,target}
eventbridge_dlq_messages{target}
redrive_attempt_total{queue}
reconciliation_mismatch_total{type}

Alarm penting:

- DLQ > 0 untuk critical queue
- oldest message age melewati SLA
- outbox unpublished age naik
- duplicate rate mendadak naik
- failure reason baru muncul
- reconciliation mismatch non-zero

Metric tanpa runbook hanya dashboard hiasan.


28. Logging Fields

Setiap log consumer minimal membawa:

messageId
eventId
effectKey
consumerName
eventType
aggregateId
aggregateVersion
correlationId
causationId
attemptNumber jika tersedia
queueName/source
decision: applied | duplicate | skipped | failed | quarantined
failureReason

Log yang buruk:

Failed to process message

Log yang baik:

{
  "level": "WARN",
  "message": "duplicate event ignored",
  "consumerName": "worklist-projector",
  "eventType": "CaseApproved",
  "eventId": "evt-88f",
  "effectKey": "worklist:case:C-1001:v18",
  "caseId": "C-1001",
  "caseVersion": 18,
  "correlationId": "corr-abc"
}

29. Testing Matrix

FailureExpected Safe Behavior
Worker crash after DB commit before ackmessage retried, consumer detects duplicate/no-op
Worker crash before DB commitmessage retried, effect applied once
Visibility timeout too shortconcurrent duplicate does not double-commit
SQS duplicate deliveryno duplicate business effect
EventBridge target retrytarget idempotent
SNS subscriber receives duplicatesubscriber idempotent
Outbox relay publishes same event twiceconsumers dedupe/effect-key no-op
DLQ redrivealready-applied effects skipped
Event replay for projectionno irreversible side effect triggered
Out-of-order eventstale version ignored/quarantined
External API timeout after requestlocal status UNKNOWN and reconciliation, not blind duplicate
Schema incompatible eventquarantine/DLQ with reason, no infinite retry
Dependency downbounded retry/backoff, backlog visible
Batch partial failuresuccessful records not double-effected on retry

30. Design Recipes

Recipe A: Queue-to-Database Worker

SQS -> Worker -> DB

Rules:

- receive message
- derive effect key
- transactionally insert effect key + apply update
- commit
- delete message
- DLQ after bounded attempts

Recipe B: Outbox-to-EventBridge

Command transaction -> outbox row -> relay -> EventBridge

Rules:

- stable eventId in outbox
- publish retry with backoff
- mark published after PutEvents success
- monitor oldest unpublished row
- consumers idempotent

Recipe C: Event-to-Projection

EventBridge/SQS -> Projector -> Read Model

Rules:

- projection update conditional on version
- duplicate event no-op
- replay mode supported
- freshness metric exposed

Recipe D: Event-to-External Side Effect

Event -> SideEffectCommand -> Worker -> External API

Rules:

- local effect record
- external idempotency token
- UNKNOWN state for timeout
- reconciliation job
- no mass replay without side-effect guard

31. Common Bad Designs

Bad Design 1: Delete Before Process

receive SQS
immediately delete
then update DB

Risk: lost effect.

Bad Design 2: Process Without Effect Key

receive event
send email

Risk: duplicate email on retry/replay.

Bad Design 3: Trust FIFO as Complete Exactly-Once

FIFO queue means no idempotency needed

Risk: crash/redrive/external duplicate.

Bad Design 4: Retry Timeout with New External Reference

payment timeout -> retry with new idempotency key

Risk: double charge.

Bad Design 5: DLQ Redrive Without Root Cause

DLQ has 100k messages -> redrive all

Risk: outage loop, duplicate effects, downstream overload.


32. Checklist untuk Review Desain

Sebelum production, jawab:

Apa delivery semantics primitive yang dipakai?
Duplicate bisa muncul dari mana?
Apa idempotency key command?
Apa effect key consumer?
Apakah duplicate check atomic dengan effect?
Kapan message di-ack/delete?
Apa yang terjadi jika worker crash setelah commit?
Apa yang terjadi jika visibility timeout habis?
Apa yang terjadi jika event out-of-order?
Apa yang terjadi jika external API timeout?
Apa yang masuk DLQ?
Siapa owner DLQ?
Bagaimana redrive dilakukan?
Bagaimana replay projection dilakukan tanpa side effect?
Apa reconciliation job-nya?
Metric dan alarm apa yang membuktikan pipeline sehat?

Kalau jawaban masih “broker menjamin exactly once”, desain belum cukup.


33. Ringkasan Mental Model

Delivery semantics adalah kontrak antara transport dan aplikasi.

At-most-once:
  duplicate tidak ada, tetapi loss mungkin.

At-least-once:
  loss diminimalkan, duplicate mungkin.

Exactly-once:
  biasanya hanya scope sempit, jangan diasumsikan end-to-end.

Effectively-once:
  efek bisnis final satu kali karena aplikasi didesain idempotent.

Rule paling penting:

Untuk efek penting, lebih baik menerima duplicate delivery yang aman
daripada kehilangan pesan yang tidak bisa dipulihkan.

Dan rule operasionalnya:

Duplicate adalah kondisi normal.
Retry adalah kondisi normal.
Replay adalah kondisi normal.
DLQ adalah workflow, bukan tempat sampah.
Reconciliation adalah safety net, bukan nice-to-have.

Referensi

Lesson Recap

You just completed lesson 14 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.