Delivery Semantics and Retry Discipline

LESSON

Event-Driven Architecture and Streaming Foundations

008 30 min intermediate REVIEW

Delivery Semantics and Retry Discipline

By the end of this lesson, you will be able to...

  • Compare at-most-once, at-least-once, and effectively-once claims as application design boundaries.

  • Trace how acknowledgments, retries, deduplication, and DLQs change user-visible side effects.

  • Review a retry policy by naming what can be lost, duplicated, delayed, or safely skipped.

Idea in one sentence: Delivery semantics are not magic broker promises; they are explicit choices about loss, duplication, acknowledgment, retry, and side-effect safety.

Core Insight

An order service publishes:

OrderPaid(order_id=O-812, payment_id=P-91, event_id=evt-202)

The email service should send one receipt.

The support dashboard should show that the order was paid.

The analytics service should count the payment.

The naive review says:

Make sure the event is delivered exactly once.

That sounds like a clean requirement. It is usually the wrong first question.

The better review starts with the side effect:

What bad thing happens if this message is lost?
What bad thing happens if this message is delivered twice?
Who records enough state to make a retry safe?
When should we stop retrying and ask for human or operational review?

Delivery semantics are boundary claims. They say what this part of the system is allowed to lose, duplicate, retry, or quarantine. A broker can provide useful guarantees, but the application still owns the user-visible consequence.

For this lesson, use one rule:

At-least-once delivery requires duplicate-safe consumers.
Retry discipline decides which duplicates are acceptable and which failures need a different path.

What You Can Now See

The first seven lessons gave us the pieces:

Earlier idea What it contributes here
Events and commands Is this message a durable fact, an instruction, or a technical signal?
Producers and brokers Who writes the message, who stores it, and who acknowledges it?
Boundaries and ownership Which service owns the fact and which service owns the side effect?
Topologies and routing Who receives a copy and who competes for work?
Ordering and partitioning Which events must stay in sequence together?
Idempotency Which repeated side effect must become harmless?

This review combines them into one delivery question:

What does the system promise when a message moves, fails, and moves again?

Plain meaning:

Delivery semantics describe what the sender, broker, and consumer can claim about a message reaching its destination.

In this scenario:

The broker may redeliver evt-202 if the email service crashes before acknowledging it.

Technical name:

That is an at-least-once delivery shape. The message should not disappear silently, but the consumer may see it more than once.

The Concepts Together

Here is the compact map.

Claim What it means in practice Common cost
At-most-once Try once. If the attempt fails after handoff, the message may be lost. Lost work is possible.
At-least-once Keep trying until the consumer acknowledges or the retry policy stops. Duplicate delivery is possible.
Effectively-once Make repeated delivery produce one intended application effect. Requires stable IDs, stored state, and careful side-effect boundaries.

At-most-once is useful when duplicates are worse than loss.

Example: a low-value telemetry point may be dropped rather than retried forever.

At-least-once is useful when loss is worse than duplicates.

Example: a payment fact should keep moving until a consumer handles it or an operator can inspect it.

Effectively-once is not the same as "the whole distributed system executed exactly once."

It means the application made one effect safe:

same event_id -> same protected side effect -> no duplicate user harm

For the receipt email, that means the email service records that evt-202 already produced receipt_sent.

Synthesis Example

Start with this delivery path:

Order service -> orders log -> email service -> email provider

The email service has a local table:

processed_events(event_id, side_effect)

Now trace one failure.

Step What happens Intermediate state Decision
1 Broker delivers evt-202 to email. No row for evt-202. Consumer may send the receipt.
2 Email service sends the receipt. User receives one email. Side effect happened.
3 Email service crashes before ack. Broker has no ack. Broker treats the delivery as uncertain.
4 Broker redelivers evt-202. Consumer sees the same event again. Consumer checks dedup state.
5 Consumer finds or writes the protected record. evt-202 -> receipt_sent. Consumer skips the duplicate side effect and acknowledges.

Output:

The user receives one receipt.
The broker may have delivered twice.
The application effect happened once.

Naive failure contrast:

If the email service sends first and records dedup state later, a crash between those actions can still create a duplicate email. If it records first and then fails before sending, it may skip a receipt that never went out.

That is why application-level delivery design needs a boundary. Some side effects need a transactional outbox, provider idempotency key, or reconciliation job. Those deeper implementation details belong in later tracks, but this track must make the risk visible.

So far:

Delivery retry handles uncertain movement.
Idempotency handles repeated observation.
Operational review handles messages that cannot be made safe by more retries.

Common Confusions

Confusion: "At-least-once means the consumer succeeded"

Why it is tempting:

The message eventually reached a consumer, so it feels complete.

Better model:

At-least-once is about delivery attempts and acknowledgments. It does not prove that every downstream side effect succeeded. The consumer may need its own durable state, idempotency key, or repair path.

Confusion: "Retries are always safer than dropping"

Why it is tempting:

Retries look like persistence. If something failed, trying again feels responsible.

Better model:

Retries help transient failures. They can make permanent failures louder and can multiply duplicate side effects. A retry policy needs a stop condition, a backoff rule, and a destination for messages that need review.

Confusion: "A DLQ fixes bad events"

Why it is tempting:

Moving a poison event into a dead-letter queue clears the main consumer path.

Better model:

A DLQ is a quarantine and investigation boundary. It preserves evidence and protects the hot path, but someone still needs to inspect, repair, replay, skip, or compensate.

Retrieval Check

Check: A consumer receives OrderPaid twice because it crashed before acknowledging the first delivery. Which claim did the broker probably provide: at-most-once or at-least-once?

Think first, then reveal.

Answer: At-least-once. The broker avoided silent loss by redelivering after an uncertain acknowledgment. The application must make the consumer side effect duplicate-safe.

Check: A retry policy keeps retrying a permanently invalid event every second. Lag grows and no other events for that partition move. What review decision is missing?

Think first, then reveal.

Answer: The policy is missing a stop condition and a quarantine path, such as a DLQ with evidence for review. More retries do not fix a permanent validation failure.

Trade-offs and Limits

Delivery discipline improves reliability by turning hidden uncertainty into explicit choices.

It costs storage, code, and operational attention.

This helps when:

It costs:

It does not solve:

Signals to watch:

Signal Likely review question
Duplicate emails, charges, or tickets Which side effect lacks an idempotency boundary?
Growing retry count for one event Is this transient, permanent, or ambiguous completion?
DLQ volume rising Are producers publishing invalid events, or are consumers too strict?
Consumer lag grows during retries Should retries move out of the hot path or use backoff?
Operators cannot replay safely Which side effects need replay protection before retry?

The trade-off is not "stronger is always better."

The trade-off is:

Less loss usually means more duplicate handling.
Less duplication usually means more state and stricter boundaries.
Less operational noise usually means clearer retry budgets and quarantine rules.

Transfer Challenge

Review this policy:

Event: PaymentCaptured(order_id, payment_id, event_id)
Consumer: fulfillment service
Side effect: reserve stock and create shipment request
Policy: retry every failure forever until the consumer returns success
Dedup state: none
DLQ: none

Write a better review note.

A good answer should mention:

Model answer:

This should not retry forever without deduplication. Losing PaymentCaptured may leave a paid order unfulfilled, so at-least-once delivery is reasonable. But reserving stock and creating shipment requests are user-visible side effects, so the consumer needs a stable key such as payment_id or event_id for deduplication. Transient database or provider failures can retry with backoff and a retry budget. Permanent validation failures, impossible inventory states, or repeated ambiguous provider results should move to a DLQ or operational review path. Watch duplicate shipment requests, stock reservations for the same payment, rising retry counts, and lag caused by one poison event.

What Comes Next

This review closes the first half of the track.

You can now ask a useful delivery question before adding more process design:

If this event moves twice, arrives late, or cannot be processed, who owns the consequence?

The next lesson uses that question to compare choreography and orchestration. Once many services react to events, delivery safety is not enough. The system also needs visibility into the business process.

Resources

Key Takeaways

PREVIOUS Ordering, Partitioning, and Idempotency Boundaries NEXT Choreography, Orchestration, and Process Visibility