Skip to content

Instantly share code, notes, and snippets.

@pavelfomin
Last active July 10, 2024 23:47
Show Gist options
  • Save pavelfomin/b53eb89a03f5d515e440f7c45a601080 to your computer and use it in GitHub Desktop.
Save pavelfomin/b53eb89a03f5d515e440f7c45a601080 to your computer and use it in GitHub Desktop.
Kafka Message Delivery Guarantees

Kafka

Idempotency

Message Delivery Guarantees

There are three types of semantic guarantees Apache Kafka® provides between the broker, producers and consumers. Semantic guarantee refers to how the broker, producer and consumer agree to share messages.

  • At most once: Messages are delivered once, and if there is a system failure, messages may be lost and are not redelivered.

  • At least once: Messages are delivered one or more times. If there is a system failure, messages are never lost, but they may be delivered more than once.

  • Exactly once: This is the preferred behavior in that each message is delivered once and only once. Messages are never lost or read twice even if some part of the system fails.

See Message Delivery Guarantees for more details.

Support in Spring for Apache Kafka

See how Spring for Apache Kafka supports transactions and implements Exactly Once Semantics.

Notes.

  • Transactions are enabled by providing the DefaultKafkaProducerFactory with a transactionIdPrefix.

    • For applications running with multiple instances, the transactionIdPrefix must be unique per instance.

    • With Spring Boot, it is only necessary to set the spring.kafka.producer.transaction-id-prefix property - Boot will automatically configure a KafkaTransactionManager bean and wire it into the listener container.

  • For a read → process → write sequence, it is guaranteed that the sequence is completed exactly once. (The read and process have at least once semantics).

  • Starting with versions 2.5.17, 2.6.12, 2.7.9 and 2.8.0, if the commit fails on the synchronized transaction (after the primary transaction has committed), the exception will be thrown to the caller. Previously, this was silently ignored (logged at debug). Applications should take remedial action, if necessary, to compensate for the committed primary transaction.

Relevant Kafka configuration

Producer

Consumer

Retry message processing

See RetryTopicConfigurer and @RetryableTopic for the details on how to accomplish a distributed retry / DLT pattern in a non-blocking fashion (at the expense of ordering guarantees). See DefaultErrorHandler for blocking retry implementation. Some examples are available at https://www.baeldung.com/spring-retry-kafka-consumer.

@RetryableTopic usage

The following configuration worked well.

@RetryableTopic(autoCreateTopics = "false")

Setting autoCreateTopics to false enables either use manually created topics or auto create topics by the broker using default partition number. It's important for the retry/dlt topics partition number to match the source topic configuration.

The default value of attempts is set to 3 which is reasonable and results in the 3 addtional topics used:

  • {sorce-topic}-retry-0
  • {sorce-topic}-retry-1
  • {sorce-topic}-dlt

Configure retry and DLT topic names

While it would have been more convenient to provide a configuration function reference in the annotation, overwriting the topic names is not too difficult. Spring Boot 3 example:

@Bean
public RetryTopicComponentFactory customRetryTopicComponentFactory() {

    return new RetryTopicComponentFactory() {

        @Override
        public RetryTopicNamesProviderFactory retryTopicNamesProviderFactory() {
            return new CustomRetryTopicNamesProviderFactory();
        }
    };
}

static class CustomRetryTopicNamesProviderFactory implements RetryTopicNamesProviderFactory {

    @Override
    public RetryTopicNamesProvider createRetryTopicNamesProvider(
                DestinationTopic.Properties properties) {

        if(properties.isMainEndpoint()) {
            return new SuffixingRetryTopicNamesProvider(properties);
        }
        else {
            return new SuffixingRetryTopicNamesProvider(properties) {

                @Override
                public String getTopicName(String topic) {
                    return "my-prefix-" + super.getTopicName(topic);
                }

            };
        }
    }

}

See https://stackoverflow.com/questions/73284479/spring-kafka-retrytopicconfiguration-with-custom-names-for-retry-and-dead-lette/78731312#78731312

Re-processing events

If a consumer group is changed (to re-process messages), not only messages from the source topic are re-processed but also those from the re-try topics.

With attempts set to 3 and the 3 addtional topics used:

  • {sorce-topic}-retry-0
  • {sorce-topic}-retry-1
  • {sorce-topic}-dlt

not only all of the messages from the source topic are processed but also all of messages previsously re-tried from {sorce-topic}-retry-0 and from {sorce-topic}-retry-1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment