-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-9181; Maintain clean separation between local and group subscriptions in consumer's SubscriptionState #7941
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
Changes from 4 commits
63aa4e3
4384fbc
797aa7c
6f574ea
57765e1
18a4917
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -211,6 +211,7 @@ public String protocolType() { | |
| protected JoinGroupRequestData.JoinGroupRequestProtocolCollection metadata() { | ||
| log.debug("Joining group with current subscription: {}", subscriptions.subscription()); | ||
| this.joinedSubscription = subscriptions.subscription(); | ||
| subscriptions.resetGroupSubscription(); | ||
| JoinGroupRequestData.JoinGroupRequestProtocolCollection protocolSet = new JoinGroupRequestData.JoinGroupRequestProtocolCollection(); | ||
|
|
||
| List<String> topics = new ArrayList<>(joinedSubscription); | ||
|
|
@@ -689,7 +690,6 @@ protected void onJoinPrepare(int generation, String memberId) { | |
| } | ||
|
|
||
| isLeader = false; | ||
| subscriptions.resetGroupSubscription(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess we could restore this location with the change to the group subscription maintenance? I don't feel too strongly since the new location works also.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hachikuji Thanks for the review. Restored this so that the only change from this PR is the clean separation of group and local subscriptions |
||
|
|
||
| if (exception != null) { | ||
| throw new KafkaException("User rebalance callback throws an error", exception); | ||
|
|
@@ -1357,7 +1357,7 @@ private static class MetadataSnapshot { | |
|
|
||
| private MetadataSnapshot(SubscriptionState subscription, Cluster cluster, int version) { | ||
| Map<String, Integer> partitionsPerTopic = new HashMap<>(); | ||
| for (String topic : subscription.groupSubscription()) { | ||
| for (String topic : subscription.metadataTopics()) { | ||
| Integer numPartitions = cluster.partitionCountForTopic(topic); | ||
| if (numPartitions != null) | ||
| partitionsPerTopic.put(topic, numPartitions); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -183,12 +183,6 @@ private boolean changeSubscription(Set<String> topicsToSubscribe) { | |
| return false; | ||
|
|
||
| subscription = topicsToSubscribe; | ||
| if (subscriptionType != SubscriptionType.USER_ASSIGNED) { | ||
| groupSubscription = new HashSet<>(groupSubscription); | ||
| groupSubscription.addAll(topicsToSubscribe); | ||
| } else { | ||
| groupSubscription = new HashSet<>(topicsToSubscribe); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
|
|
@@ -208,7 +202,7 @@ synchronized boolean groupSubscribe(Collection<String> topics) { | |
| * Reset the group's subscription to only contain topics subscribed by this consumer. | ||
| */ | ||
| synchronized void resetGroupSubscription() { | ||
| groupSubscription = subscription; | ||
| groupSubscription = Collections.emptySet(); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -335,22 +329,24 @@ public synchronized Set<TopicPartition> pausedPartitions() { | |
| } | ||
|
|
||
| /** | ||
| * Get the subscription for the group. For the leader, this will include the union of the | ||
| * subscriptions of all group members. For followers, it is just that member's subscription. | ||
| * This is used when querying topic metadata to detect the metadata changes which would | ||
| * Get the subcription topics for which metadata is required . For the leader, this will include | ||
| * the union of the subscriptions of all group members. For followers, it is just that member's | ||
| * subscription. This is used when querying topic metadata to detect the metadata changes which would | ||
| * require rebalancing. The leader fetches metadata for all topics in the group so that it | ||
| * can do the partition assignment (which requires at least partition counts for all topics | ||
| * to be assigned). | ||
| * | ||
| * @return The union of all subscribed topics in the group if this member is the leader | ||
| * of the current generation; otherwise it returns the same set as {@link #subscription()} | ||
| */ | ||
| synchronized Set<String> groupSubscription() { | ||
| return this.groupSubscription; | ||
| synchronized Set<String> metadataTopics() { | ||
| Set<String> topics = new HashSet<>(groupSubscription); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might borderline overkill, but I'm considering if we could avoid the copy here with logic like the following: Basically relying on the the group subscription being a superset of the local subscription when it is defined.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hachikuji Thanks for the review, updated. I had to update one unit test since it wasn't using a superset for group subscription, but think that is ok. |
||
| topics.addAll(subscription); | ||
| return topics; | ||
| } | ||
|
|
||
| synchronized boolean isGroupSubscribed(String topic) { | ||
| return groupSubscription.contains(topic); | ||
| synchronized boolean needsMetadata(String topic) { | ||
| return subscription.contains(topic) || groupSubscription.contains(topic); | ||
| } | ||
|
|
||
| private TopicPartitionState assignedState(TopicPartition tp) { | ||
|
|
||
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'm confused about a couple things. First, if a subscription changes while we are in the middle of a rebalance, do we need to call
onJoinPrepareagain? With the old semantics, we probably didn't need to because we revoked everything, but with "cooperative" rebalancing, perhaps we should? cc @guozhangwangThe second thing is that it seems surprising that we raise auth errors from the "group subscription." If consumer 1 has subscribed to "A" and consumer 2 has subscribed to "B," I probably wouldn't expect to get any auth errors for topic "B" from consumer 1. So maybe we should be ignoring auth errors not part of
subscription()? Not sure if that is straightforward or if there are any additional implications.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.
@hachikuji Thanks for the review. If we decide that we always need to do
onJoinPrepare, then we won't need this fix.It is odd to propagate authorization failures from the group subscription since that feels more like an implementation detail. But at the same time, if we don't get metadata for some topics, is there any point in adding topics from group subscription to the ConsumerMetadata? We probably need to update javadoc for poll() if we don't change the current behaviour.
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.
Regarding the first question, if a subscription changes while we are in the middle of a rebalance we would call
onPartitionsRevokedimmediately on those still-assigned partitions that are not part of the new subscription any more, and for the returned assignment we check if they match our join-subscription and if not we trigger another rebalance (this is pre-429). So I think we still do not need to re-triggeronJoinPreparewhen subscription changes in the middle.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.
@rajinisivaram I think I understand the problem a little better now and I'm coming around to the approach in this PR. It does make sense to me to reset the group subscription at the time that we set
joinedSubscriptioninConsumerCoordinator. Then I guess my only question is whether we still need to reset it insideonJoinPrepareas well?Another (possibly worse) idea I had is to change the implementation of
resetGroupSubscription. Perhaps instead of setting it equal to the current local subscription, we should set it to null or empty. We could try to maintain a cleaner separation between the local and group subscriptions. We would then need to updateConsumerMetadatato work with both the local and group subscription.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.
@hachikuji Thanks for the reviews. I pushed two commits. The first one just removes reset from
onJoinPreparesince we don't need it any more. I also wanted to try out your suggestion of maintaining clean separation between local and group subscriptions. The last commit does this. Do you think it is worth making that change?