Message Delivery Semantics in Event-Driven Architectures
By Misbahul Munir4 min read775 words

Message Delivery Semantics in Event-Driven Architectures

Software Architecture
microservices
event driven architecure

In the world of event-driven architecture (EDA), services communicate through events instead of direct API calls. This decoupling unlocks scalability, resilience, and flexibility—but it also introduces a critical concern: how do we ensure messages are reliably delivered and processed?

This is where message delivery semantics come into play.

In this article, we’ll explore the three core delivery guarantees—at-most-once, at-least-once, and exactly-once—and show how each affects the behavior of distributed systems. We'll also look at real-world use cases to help you decide which guarantee to choose for your architecture.


What Are Message Delivery Semantics?

Delivery semantics define how often a message is delivered and processed by a consumer, especially in the face of failures or retries.

There are three main types:

  1. At-Most-Once: The message is delivered zero or one time. No retries.
  2. At-Least-Once: The message is delivered one or more times. Retries are allowed.
  3. Exactly-Once: The message is delivered once and only once. No duplicates, no loss.

Let’s go deeper into each.


1. At-Most-Once Delivery

"Fire and forget."

In this mode, a producer sends a message, and the system makes no guarantees about whether the consumer receives it. If a network issue or crash occurs, the message may be lost forever.

Pros:

  • Fastest and most lightweight.
  • Minimal overhead.

Cons:

  • Message loss is possible.
  • No retries, no acknowledgment.

Use Case:

Logging and telemetry data—where occasional data loss is acceptable and the volume is high.

Example:

A frontend web app sends performance metrics to a collector. If a request fails due to a timeout, the event is dropped. No retries happen, which keeps the UI responsive.

Producer → [ No retry on failure ] → Consumer

2. At-Least-Once Delivery

"Try until you succeed."

This is the most common delivery mode in event-driven systems. Messages are retried until an acknowledgment is received from the consumer. This ensures no messages are lost—but can result in duplicate messages if the consumer has already processed it.

Pros:

  • Durability: No message is lost.
  • Works well with idempotent consumers.

Cons:

  • Duplicates must be handled.
  • Requires deduplication logic or idempotent operations.

Use Case:

Payment processing, where failing to process a transaction is worse than double-processing.

Example:

A hotel booking system emits a "room_reserved" event. If the consumer (e.g., notification service) crashes mid-process, the broker retries. The service must check whether the notification has already been sent to avoid spamming the user.

Producer → [ Retry until ack ] → Consumer Acknowledgment

3. Exactly-Once Delivery

"The holy grail."

With this guarantee, every message is delivered and processed once, even in the face of retries, crashes, or network failures. This is hard to achieve in distributed systems and usually comes with performance and complexity trade-offs.

Pros:

  • Clean, duplicate-free processing.
  • No need for idempotent consumer logic.

Cons:

  • Complex implementation.
  • May introduce latency and require transactional systems.

Use Case:

Banking systems or inventory management, where duplicating a transaction could have severe consequences.

Example:

A financial service consumes a "funds_transferred" event. Using Kafka’s transactional producer and consumer API, and atomic writes to a database, it ensures no transaction is applied more than once—even across retries.

Producer → [ Atomic publish + process ] → Consumer → [ Transactional DB commit ]

Comparing the Three Semantics

Delivery SemanticsGuaranteeDuplication Possible?Message Loss Possible?Common Usage
At-Most-Once0 or 1 deliveryNoYesLogs, metrics
At-Least-Once1 or more deliveriesYesNoEmail notifications, payments
Exactly-OnceExactly 1 deliveryNoNoFinancial transactions, billing

Tips for Choosing the Right Semantic

  1. Can you tolerate message loss?
    • Yes: Use at-most-once.
  2. Can your system tolerate duplicates?
    • Yes: Use at-least-once and ensure idempotent consumers.
  3. Do you need strict accuracy with no duplicates or loss?
    • Use exactly-once, but expect higher costs and complexity.

Building Idempotent Consumers

Since at-least-once is often the default in systems like RabbitMQ, Kafka, and AWS SQS, developers must design consumers to handle duplicates safely.

Example Strategy:

  • Add a unique event ID.
  • Store processed IDs in a database or cache.
  • Ignore duplicates by checking the ID before processing.
# Python-style pseudo code if not is_event_processed(event.id): process(event) mark_as_processed(event.id)

Conclusion

Choosing the right message delivery semantics is essential to building a reliable and maintainable event-driven system. While at-most-once favors speed, and at-least-once favors reliability, exactly-once strives for perfection—at a price.

In the end, it’s a trade-off between performance, complexity, and correctness. Choose wisely based on your system’s requirements.