-
Notifications
You must be signed in to change notification settings - Fork 14.9k
KAFKA-10000: Per-connector offsets topics (KIP-618) #11781
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
KAFKA-10000: Per-connector offsets topics (KIP-618) #11781
Conversation
83c3867 to
56c42e9
Compare
|
Converting to draft until upstream PRs are reviewed. |
0ce5eea to
93760f5
Compare
58956c3 to
05ca505
Compare
|
Given that all merge conflicts have been resolved and #11780 has already been approved, marking this as ready for review. |
11ed861 to
fe67568
Compare
tombentley
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @C0urante! This is a first pass. I haven't looked at tests yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this call to get could result in a leaked TopicAdmin, and thus Admin, because offsetStoreForRegularSourceTask doesn't guarantee to invoke topicAdminCreator (it calls it topicAdminSupplier) (when it does, the Admin will eventually we closed via ConnectorOffsetBackingStore.stop).
In any case I think the ownership of the Admin here (responsibility for ultimately closing it) is pretty unclear, but it's not clear to me why it needs to be. So a comment to explain would be great, if a clean up of the logic is no possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This call won't result in a leak because the AbstractWorkerSourceTask class will always close its TopicAdmin during shutdown; see AbstractWorkerSourceTask::close. And, given the idempotent nature of Admin::close, it's fine to close it from both the AbstractWorkerSourceTask and ConnectorOffsetBackingStore classes.
The underlying point about ownership being unclear is fair, though, and the control flow that creates the topic admin in this method is especially convoluted. I've added a couple comments that hopefully clear things up. I wish the logic itself could be better but as it is, things are easy to test and that's taken advantage of by adding a ton of coverage in the WorkerTest suite. Happy to refactor if there's an easily-testable approach that's clearer, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The conditions under which we actually end up calling topicAdminCreator.get() are:
sourceConfig.offsetsTopic() != null && config.connectorOffsetsTopicsPermitted() // connector-specific store
|| config.topicCreationEnable() && sourceConfig.usesTopicCreation() // task needs for topic creationThese are the only circumstances in which topicAdmin.get() would return non-null.
So I wonder if we should just put that logic directly in doBuild(), and have offsetStoreForRegularSourceTask() accept a nullable topicAdminSupplier (which will be guaranteed non-null in the case where it's actually called).
Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the tests it doesn't seem we have any that assert that the Admin actually gets closed. WorkerSourceTaskTest asserts this, but for this code we're reliant that every invocation of doBuild always returns a WorkerSourceTask which is always stopped. I can see this is pretty hard to test though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I added the logic for checking those conditions to doBuild. It's still a little clunky since we're checking regularSourceTaskUsesConnectorSpecificOffsetsStore in two places now (doBuild and offsetStoreForRegularSourceTask), but for testing purposes it's easier to work with since we can continue to unit test offsetStoreForRegularSourceTask independently without having to go through doBuild. And it's certainly more readable than the caching Supplier logic that it replaced.
Good point about ensuring that we stop WorkerSourceTask instances, we definitely do a poor job of that when we fail to build a task. In fact, this has already been documented as a bug with an accompanying fix PR. If that's worth pursuing at some point let me know and I can rebase onto the latest trunk.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! When you have time it would be great if you could rebase that PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it correct that if workerGetFuture.cancel was already cancelled, and returned true, then we won't try to cancel connectorGetFuture?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, good point. Switched to | instead of ||, which disables short-circuiting so that it's guaranteed that cancel is called on both futures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also added a comment to this part just in case someone sees that and things that a single pipe instead of a double pipe is just a typo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's quite confusing that timeout is initially in unit, but in this reassignment becomes ms. I think maybe a new timeoutRemainingMs would be clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 done.
631f08b to
ceb7c82
Compare
showuon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made a pass, left some comments. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: indent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, addressed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: indent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, addressed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we set admin = null in this case, like the comment said:
// Forget the reference to the admin so that we won't even try to use the admin the next time this method is called ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want to keep throwing the exception here if the method is called again; nullifying the admin field would stop that from happening. That does remind me though, we should probably copy the same error message from here into the code path for starting connector offset stores. I've done that in my latest push; LMKWYT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could user overrides the transactional.id to null? Should we add this test case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that can happen without causing other issues, since the user-supplied configs have come from either the JSON REST API or the Java properties worker file. The former doesn't deserializes things into a literal null and although the latter does, as of #11333, we fail validation for connector configs in that case.
But, given how little additional effort it is, I don't see a problem with adding that check. At the very least, it helps prevent a 500 instead of a 400 response during config validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constructor kinda confuses the ownership of the Admin client. I think things are cleaner when the TopicAdmin instantiates (and thus owns the admin). Note, it looks like there are no callers for TopicAdmin.admin. It seems that the call sites in doBuild could simply pass the map of configs (and the bootstrapServers looked up from that), rather than instantiating the admin and then passing it to the TopicAdmin.
Obviously the test code has slightly different requirements, meaning we still need this constructor. I did also wonder whether we could also get rid of bootstrapServers by defining toString on KafkaAdminClient and using that for the logging and exceptions here in TopicAdmin. Perhaps that's worth a followup PR at some point, (though perhaps there are benefits to hiding bootstrap servers from receivers of clients).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good points. I've:
- Demoted the visibility of the constructor that accepts an
Admininstance back to package-private - Documented that it's the caller's responsibility to close the topic admin in order to release the resources allocated for its
Admin - Removed the unused
adminmethod - Switched over to the
TopicAdmin(Map<String, Object>)constructor in theWorkerclass
I originally did this refactoring because I was managing the lifecycle of the Admin differently, but kept part of it because it made testing much easier when all you had to pass in was an Admin and a String instead of an Admin and an entire config map. It also seemed like an anti-pattern to have a constructor that accepted both an already-instantiated client object, and the configuration for that client object.
|
Hey guys--thanks for the reviews, really appreciate the rapid responses here. I found a bug that's been a bit trickier to solve than expected and have had little time to work on it this week. I plan to push the next draft by Friday at the very latest. If it matters, the bug is that the offset stores for regular (non-exactly-once) source tasks, and source connectors, are never started. I'm planning on fixing that first, then adding an integration test case to #11782 to simulate a soft downgrade where someone disables exactly-once support on their worker after creating a connector and letting it run for a bit, and finally, manually auditing the changes for KIP-618 to catch any other potential bugs related to improper initialization or cleanup of resources. |
ceb7c82 to
0a6f219
Compare
|
Pushed the next draft which should fix the offset store startup bug and address outstanding review comments. |
0a6f219 to
64e9628
Compare
|
I've completed the remaining follow-up tasks. Audit of resource allocation and cleanup:
Integration testing
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that we close the TopicAdmin for source tasks in AbstractWorkerSourceTask::close here, this may seem redundant. However, I've chosen to keep it in for two reasons:
- The
WorkerConnectorclass has no access to theTopicAdminused for its offset store; we'd have to add another constructor parameter to that class and manually close theTopicAdminduring shutdown if we removed this line - It seems safer to leave this in. Although
Autocloseable::closeis not required to be idempotent, it is strongly recommended, and since all current calls toConnectorOffsetBackingStore::stopare wrapped inUtils::closeQuietlyanyways, the risks are low. On the other hand, by leaving this out, we would make it easier to introduce a resource leak in the future, which would have more serious consequences.
tombentley
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @C0urante, I left a few more comment, but they're nits at this stage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we could improve this slightly by not comparing as strings, but as Set<String>. That way at least the order wouldn't matter, which it looks like it does right now.
I've wondered whether we should add some kind of cluster.id to the client configs which could be used to assert that the client was connected to the expected cluster. If we did do that, and they were specified in the configs here, then you could safely know at this point whether they were the same cluster without needing to connect at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, we can make this order-independent by comparing sets. Not too much work, so I've done that.
I have a long-term vision of relocating this logic into the WorkerTask class (or its subclasses) so that we can ping the remote Kafka cluster, grab its ID there, and decide at that point whether or not we need to construct an additional offsets store, all without having to worry about blocking up the herder tick thread. This would obviate the need for users to do any additional work at all, but it would require a significant refactoring of this part of the code base, so I've held off on it for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might newTopicSpecification be a better name? It's just the "description" is a bit ambiguous: it could be describing an existing topic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, done
connect/runtime/src/main/java/org/apache/kafka/connect/storage/KafkaOffsetBackingStore.java
Outdated
Show resolved
Hide resolved
connect/runtime/src/main/java/org/apache/kafka/connect/storage/KafkaOffsetBackingStore.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great doc, but maybe we should also have some on those constructors themselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 good point, done
02cf6c6 to
ef5a43b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discovered this bug while working on #12307 for https://issues.apache.org/jira/browse/KAFKA-14006.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made another pass, left some minor comments. Overall LGTM. During the review, I found a bug KAFKA-14012. But it's not related to this change and can be fixed in another PR. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've add all bootstrap servers into producerBootstrapServers set in L1672, why should we re-create another hashSet here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, left in accidentally. Removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OffsetBackingStore#start() -> OffsetBackingStore#stop() ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and [close] the {@link TopicAdmin} used by that store.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did we log trace to this primary store error, but warn to the secondary store error in L290?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The failure to write to the primary store will probably be logged elsewhere at a higher level, since we report it to the callback. We swallow failures to write to the secondary store, so this is the only chance we get to log those.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used here.
Added this. on writes to this field and others in the configure method to make it easier to identify instance vs. function variables.
ef5a43b to
c6918fe
Compare
showuon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
|
Failed tests are unrelated: |
Implements support for per-connector offsets topics as described in KIP-618. Reviewers: Luke Chen <showuon@gmail.com>, Tom Bentley <tbentley@redhat.com>
Implements support for per-connector offsets topics as described in KIP-618.
Relies on changes from: