Skip to content

Add auto-configuration for Kafka Retry Topics #29812

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from

Conversation

tomazfernandes
Copy link
Contributor

Closes #28450

This PR adds basic auto-configuration capabilities for Spring Kafka's non-blocking delayed retries feature (https://docs.spring.io/spring-kafka/reference/html/#retry-topic).

I chose not to add exception type classification to it just now as was previously suggested by @garyrussell so I can get feedback on the basic solution first. I can add exception type classification in a separate PR if and when this one is approved.

I wasn't sure whether to add an entry to the Messaging section of the documentation, since it seems to cover more basic use cases. I can do so if that's the case.

Seems like we should add the new properties to the A.8. Integration Properties section of the documentation, but I couldn't really find it in the source code. I can add it if you point me to it.

Please feel free to request any changes you see fit, or to make a polish commit on top of it.

As always, If @garyrussell and @artembilan can take a look into this it'd be great.

Thanks

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Feb 15, 2022
Copy link
Member

@snicoll snicoll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. I've made some suggestions for your consideration.

Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add the new properties to the A.8. Integration Properties section of the documentation

That part of the doc is auto-generated by source code. So, whatever you have added to KafkaProperties is going to be reflected over there as well.

@snicoll snicoll added the status: waiting-for-feedback We need additional information before we can continue label Feb 15, 2022
@tomazfernandes
Copy link
Contributor Author

Thank you very much for the reviews. I've made the changes as suggested.

I removed the BackOff inner class since it'd have the same problem of creating a group with a dash in the name. Also, most of the BackOff creation logic and JavaDocs are from the Spring Retry project, not sure if we should add proper credit there somehow.

Let me know if there are any other suggestions or anything else to change.

Thanks.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Feb 15, 2022
@snicoll snicoll added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged status: feedback-provided Feedback has been provided labels Feb 21, 2022
@snicoll snicoll added this to the 2.7.x milestone Feb 21, 2022
@wilkinsona wilkinsona added the status: blocked An issue that's blocked on an external project change label Mar 4, 2022
@wilkinsona wilkinsona removed the status: blocked An issue that's blocked on an external project change label Apr 11, 2022
.maxDelay(retryTopic.getMaxDelayMillis()).multiplier(retryTopic.getMultiplier())
.random(retryTopic.isRandomBackOff()).build();
Assert.isInstanceOf(SleepingBackOffPolicy.class, policy,
() -> "BackOffPolicy must be an instance of SleepingBackOffPolicy. Provided: " + policy);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is OK to say this: the maximum what end-user can provide are exactly those props you use for BackOffPolicyBuilder.
So, we might need to ensure that only SleepingBackOffPolicy is created. Perhaps just ignore some props which are not sufficient...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into this @artembilan. Currently, the BackOffPolicyBuilder only returns instances of SleepingBackOffPolicy, but I didn't want to add this constraint to the builder's method signature.

So this assertion here is just for the case that, if we in the future update the Builder to for example return a NoBackOffPolicy if delay is set to 0, we don't get a class cast exception, but get this error instead.

But I agree that this is not a really useful message to the user. Maybe we should just blindly cast? Not sure what a useful message for the users should be in this case, since it'd not really be their fault.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't support a delay == 0, then it has to be rejected as a configuration properties validation, before calling this builder.
What I mean, if we don't support anything else in the builder, then we need to ensure the props provided for it are in the valid range.
The message you say now is fully misleading: end-user does not create instances of this, so the message (if any), must really point what is wrong with end-user config.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, currently if the provided delay < 1 it is set to 1 in the FixedBackOffPolicy, and most or all other BackOffPolicy implementations follow that pattern. In RetryTopic's annotation processing and builder I chose to mimic Spring Retry behavior as much as possible, since we relied on the same @BackOff annotation.

I've been meaning to ask if maybe we should change this in Spring Retry to return the NoBackOffPolicy instead if delay=0 is provided, but didn't want to mix things up. Also, that seems like a breaking change, so it should be a good fit only for SR 2.0.

Maybe it'd be worth it to validate user input here to make sure the inputs are > or >= 0. Although it might be confusing having different behaviors for auto configuration and the @RetryableTopic annotation and builder.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, why then does RetryTopicConfigurationBuilder support only a SleepingBackOffPolicy?
Why it cannot be that super BackOffPolicy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's because the way we generate the delay values is we inject a Sleeper implementation in the BackOffPolicy that calls backOff and stores the values, so we need that any custom policy the user provides respect the SleepingBackOffPolicy contract.

The builder offers a noBackOff() method if that's what the user desires, and if that's the case we don't need the Sleeper to fetch the values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see there public RetryTopicConfigurationBuilder noBackoff() {, so perhaps with some props config we can fall back to this instead of customBackoff() then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean, if users set delay=0 we could set noBackOff() for them there? That's an interesting thought. My concern would be having different behaviors in auto configuration and annotation processing - would that be confusing for users? I guess since these are kind of corner cases, maybe we can depart from that and also add properties validation.

WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would be reasonable.

Thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I’ll add that then. Thanks a lot for your input!

.suffixTopicsWithIndexValues().doNotAutoCreateRetryTopics().create(kafkaOperations);
}

private SleepingBackOffPolicy<?> getBackOffPolicy(Topic retryTopic) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static

/**
* In the exponential case (multiplier() > 0) set this to true to have the
* backoff delays randomized, so that the maximum delay is multiplier times
* the previous delay and the distribution is uniform between the two values.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A description for these configuration properties must be as short as possible: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.configuration-metadata.format.property

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. This description was extracted from the @BackOff annotation, I'll try to come up with something more succinct. Thanks.

.maxDelay(retryTopic.getMaxDelayMillis()).multiplier(retryTopic.getMultiplier())
.random(retryTopic.isRandomBackOff()).build();
Assert.isInstanceOf(SleepingBackOffPolicy.class, policy,
() -> "BackOffPolicy must be an instance of SleepingBackOffPolicy. Provided: " + policy);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, why then does RetryTopicConfigurationBuilder support only a SleepingBackOffPolicy?
Why it cannot be that super BackOffPolicy?

@tomazfernandes
Copy link
Contributor Author

Implemented @artembilan's suggestions.

I looked into using @Valid to validate the input, but since a couple properties are Duration and there doesn't seem to be a way to properly validate since annotations don't allow such value types, I preferred to keep it simple and validate all properties together in the code.

Please let me know if there's a better way or anything else to change.

Thanks

@tomazfernandes tomazfernandes changed the base branch from main to 2.7.x April 16, 2022 02:27
Add properties validation
Add noBackOffPolicy for zero delay
Improve javadocs
@tomazfernandes
Copy link
Contributor Author

I've cleaned up the validation logic and added tests for that. Please let me know if there's anything else. Thanks!

@@ -1447,6 +1454,23 @@ public void setRandomBackOff(Boolean randomBackOff) {
this.randomBackOff = randomBackOff;
}

private Topic validate() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't mind, but I don't know Spring Boot development process in details, so I'll defer a decision for a proper validation and its place to respective Spring Boot team members.
Thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, thanks. Just for reference, it seems we already have some validation in this buildProperties method of the KafkaProperties class, so I thought it seemed ok there. Of course, I'm ok with validating any other way or place.

public Map<String, Object> buildProperties() {
validate();
Properties properties = new Properties();

Thanks for looking into this.

@tomazfernandes
Copy link
Contributor Author

FYI, this unrelated test failed in the PR build:

Found test failures in 1 test task:
[00:00:08](https://ci.spring.io/builds/185388#L62321ef2:5476)

[00:00:08](https://ci.spring.io/builds/185388#L62321ef2:5477)
:spring-boot-project:spring-boot-devtools:test
[00:00:08](https://ci.spring.io/builds/185388#L62321ef2:5478)
    org.springframework.boot.devtools.tunnel.server.HttpTunnelServerTests > disconnectTimeout()`

I'll force-push a no-ops commit to trigger the PR build again. Thanks.

Add validation for randomBackOff with policy not exponential

Add test case
@tomazfernandes
Copy link
Contributor Author

@garyrussell, if and when possible, I'd like your input on the chosen sensible defaults, since it's something not so easy to change later.

Now we have:

useSingleTopicForFixedDelays()
suffixTopicsWithIndexValues()
doNotAutoCreateRetryTopics()

With these settings, by default users will get 3 topics, independently of the number of attempts or delay value:
myTopic, myTopic-retry and myTopic-dlt

If a multiplier or maxDelay is set, yelding multiple delay values, users will get one topic per attempt plus the dlt:
myTopic, myTopic-retry-0, myTopic-retry-1, myTopic-retry-2 ... myTopic-dlt

The other defaults are the same as for the rest of the feature, maxAttempts = 3 and FixedBackOff with delay=1s.

Thanks.

@tomazfernandes
Copy link
Contributor Author

Hi @snicoll and @wilkinsona, looks like from the Spring Kafka team's perspective there are no further concerns. Is there anything you'd like to be changed implementation-wise? We can revise any part of this based on user feedback for 2.7.

Thanks

@snicoll
Copy link
Member

snicoll commented Apr 20, 2022

Thanks for the follow-up and for working with the team.

Time is running out with RC1 happening tomorrow. I will do my best to review it before that happens if possible.

@snicoll snicoll self-assigned this Apr 20, 2022
@tomazfernandes
Copy link
Contributor Author

Sure, no worries. I should be available if there's anything to be changed prior to the release. Thanks.

@snicoll snicoll modified the milestones: 2.7.x, 2.7.0-RC1 Apr 21, 2022
snicoll pushed a commit that referenced this pull request Apr 21, 2022
snicoll added a commit that referenced this pull request Apr 21, 2022
@snicoll snicoll changed the title Add auto-configuration to Kafka Retry Topics Add auto-configuration for Kafka Retry Topics Apr 21, 2022
@snicoll snicoll closed this in 76e197f Apr 21, 2022
@snicoll
Copy link
Member

snicoll commented Apr 21, 2022

@tomazfernandes thank you for making your first contribution to Spring Boot. If you're interested, I've polished your contribution in b3e3581, mostly about using primitive types if possible. I also decided to remove the validation business as we don't really do such a thing in configuration properties usually. We'd rather let the underlying component we configure throw an exception if necessary.

@tomazfernandes
Copy link
Contributor Author

Thanks a lot @snicoll for finding the time to look into this in such short notice! The retryable topics feature has been gaining a lot of traction with the community and with auto configuration I'm sure more users will be able to benefit from it and in a simpler way.

I had missed that if users had more than one KafkaTemplate it'd throw an exception. If I understand correctly, now with @ConditionalOnSingleCandidate(KafkaTemplate.class) if users have more than one KafkaTemplate bean defined the retry topic configuration will back off. Is that correct?

I wonder if it would make sense to add a kafkaTemplate configuration property that defaults to "kafkaTemplate", and then fetch the template bean by name, so that users can still benefit of the feature by leveraging auto configuration's KafkaTemplate if that's the case, or set a different one to be used in case they have more than one defined. Is this something you usually do in auto configuration?

Or maybe that's expected behavior - if users choose to define multiple KafkaTemplate beans, they might just as well define RetryTopicConfiguration beans. We'd just need to properly document that.

What do you think?

Thanks again!

@snicoll
Copy link
Member

snicoll commented Apr 21, 2022

That's not what @ConditionalOnSingleCandidate does. A single KafkaTemplate should be defined (so either one in the context, or one flagged with @Primary in case of multiple instnaces). This is is consistent with what we do elsewhere and the recommendation is indeed to configure the feature if a "main" KafkaTemplate cannot be determined. The auto-configuration report will mention why the feature wasn't configured (again, something users rely on elsewhere) so I don't think it needs an explicit mention in the doc.

@tomazfernandes
Copy link
Contributor Author

I see, makes sense. I've learned a lot about autoconfiguration and what it is and isn't supposed to do in this issue, so thanks a lot everyone involved.

Thanks again @snicoll for looking into this in time for the RC release!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add AutoConfiguration to Spring Kafka's Retry Topic Feature
5 participants