In a publish/subscribe model in microservices, how to receive/consume a message only once per service type
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
In a microservices architecture, the publish/subscribe communication model enables efficient message distribution among the various services in a system. This model facilitates asynchronous communication, where publishers send messages without needing to know who the subscribers are. Subscribers receive messages based on the topics or events they are subscribed to. A common challenge in such systems is ensuring that each message is consumed by each service type exactly once. This is crucial to prevent duplicate processing or introduce inconsistencies.
Understanding Message Delivery Semantics
Three primary types of message delivery semantics exist in distributed systems:
- At-most-once: Each message is delivered once or not at all. Risk of message loss.
- At-least-once: Messages are guaranteed delivery but might be delivered more than once, causing duplicates.
- Exactly-once: Each message is guaranteed to be delivered exactly once – the gold standard for many business operations but the most challenging to achieve.
Ensuring Exactly-Once Delivery in Publish/Subscribe Models
Use of Idempotence
An operation is considered idempotent if repeating it doesn’t change the result beyond the initial application. When a microservice is designed to handle idempotent operations, even if it receives a message more than once, the final state of the service remains consistent.
Example: In a banking service, instead of a message saying "add 1,100." If this message is processed multiple times, the balance remains correct.
Deduplication Mechanisms
Implementing deduplication involves storing the identifiers of processed messages (IDs) and checking each incoming message against these IDs.
Example: A service might maintain a cache or a database table with the IDs of all messages it has processed in the last period. If a new message has an ID already in this cache, it is ignored.
Message Ordering
Ensuring that messages are processed in the order they are sent is crucial to maintaining system integrity, especially in systems requiring sequential consistency.
Example: A sequence number can be added to each message. Services process messages only in increasing order of their sequence numbers.
Using Transactions
Transactions ensure that either all operations succeed or none do. By wrapping message consumption and the business logic into a transaction, we can roll back both if something fails, preventing inconsistent states.
Example: In a distributed database, consuming a message and updating records can be part of the same transaction.
Technologies Supporting Exactly-Once Delivery
Several messaging systems provide tools and features to aid in achieving exactly-once delivery:
- Apache Kafka: Offers exactly-once semantics through its idempotent producer and transactional APIs.
- RabbitMQ: Utilizes publisher confirms and consumer acknowledgments to ensure messages are not lost and are processed once.
- AWS SQS: Supports at-least-once delivery and deduplication can be implemented manually.
Challenges and Trade-offs
Achieving exactly-once delivery comes with trade-offs regarding complexity, performance, and resource usage. For instance, maintaining a deduplication cache requires additional storage and can introduce latency.
Summary Table
The following table summarizes key considerations for receiving/consuming a message only once per service type in a publish/subscribe model:
| Consideration | Technique | Description | Applicable Technology |
| Idempotence | Functional Design | Making operations re-runnable without state changes after the initial run. | All |
| Deduplication | Cache/Database | Storing message IDs that are processed and checking new messages against them. | Kafka, RabbitMQ, Custom solutions |
| Message Ordering | Sequence Numbers | Ensuring messages are processed in the exact order they’re sent by using sequence numbers. | Kafka, Custom solutions |
| Transactions | Atomic Operations | Using transactions to wrap message consumption and business logic. | Kafka, Databases |
Conclusion
Achieving exactly-once delivery in a microservices environment using a publish/subscribe model is complex, requiring a combination of good design principles, proper tooling, and considerations for trade-offs. By applying idempotence, deduplication, message ordering, and transactional consistency, systems can ensure reliable and consistent message consumption across different service types.

