This article clarifies the scope of Kafka's built-in exactly-once semantics, explaining that it primarily guarantees message delivery within the Kafka cluster. It emphasizes that achieving true end-to-end exactly-once processing when integrating with external systems (like databases or APIs) requires additional architectural patterns such as idempotent consumers, the transactional outbox pattern, and offset-aware idempotency.
Read original on Dev.to #systemdesignKafka's exactly-once semantics (EOS), introduced in version 0.11.0, offers strong guarantees for message delivery within the Kafka ecosystem. It operates on two main levels: producer idempotence prevents duplicates from producer retries, ensuring a message is written to a partition once, and transactions allow atomic writes to multiple partitions, visible to consumers only upon commit. However, a common misconception is that these features extend automatically to external systems integrated with Kafka consumers.
Key Boundary
Kafka's exactly-once guarantee is explicitly scoped to the Kafka cluster itself. The moment a consumer interacts with an external system (e.g., a database, a REST API, or another queue), the built-in transactional boundary of Kafka is broken.
Consider a typical Kafka consumer that polls messages, processes them, writes to a database, and then commits its Kafka offset. If the consumer crashes after successfully writing to the database but before committing the offset, Kafka will re-deliver the message. Without careful design, this leads to duplicate writes in the external database, violating the end-to-end exactly-once expectation. Simply enabling producer idempotence for subsequent Kafka writes by the consumer does not resolve this specific issue.
These patterns are crucial for building robust, fault-tolerant distributed systems that rely on message queues like Kafka, especially when dealing with financial transactions or other critical data where duplicate processing is unacceptable.
While providing stronger guarantees, Kafka's transactional features and the suggested end-to-end patterns introduce overhead. Kafka transactions require `min.insync.replicas=2`, enforcing synchronous replication and increasing write latency. Transaction coordinators add round-trip delays. Implementing these patterns typically results in a measurable performance impact (e.g., 15-20% throughput reduction with higher p99 latencies), which must be evaluated as an acceptable trade-off for correctness and reliability.