-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-10085: correctly compute lag for optimized source changelogs #8787
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 all commits
50aadc7
699d8de
cf228fb
a50a599
ecebe9a
da7ea8f
bbd8b49
6f1260b
cada478
85acb83
3863bb2
303a6e7
8d22694
0be5bb9
bce2458
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 |
|---|---|---|
|
|
@@ -28,9 +28,11 @@ | |
| import org.apache.kafka.common.Cluster; | ||
| import org.apache.kafka.common.Configurable; | ||
| import org.apache.kafka.common.KafkaException; | ||
| import org.apache.kafka.common.KafkaFuture; | ||
| import org.apache.kafka.common.Node; | ||
| import org.apache.kafka.common.PartitionInfo; | ||
| import org.apache.kafka.common.TopicPartition; | ||
| import org.apache.kafka.common.errors.TimeoutException; | ||
| import org.apache.kafka.common.utils.LogContext; | ||
| import org.apache.kafka.common.utils.Time; | ||
| import org.apache.kafka.common.utils.Utils; | ||
|
|
@@ -72,7 +74,8 @@ | |
|
|
||
| import static java.util.Comparator.comparingLong; | ||
| import static java.util.UUID.randomUUID; | ||
| import static org.apache.kafka.streams.processor.internals.ClientUtils.fetchEndOffsets; | ||
| import static org.apache.kafka.streams.processor.internals.ClientUtils.fetchCommittedOffsets; | ||
| import static org.apache.kafka.streams.processor.internals.ClientUtils.fetchEndOffsetsFuture; | ||
| import static org.apache.kafka.streams.processor.internals.assignment.StreamsAssignmentProtocolVersions.EARLIEST_PROBEABLE_VERSION; | ||
| import static org.apache.kafka.streams.processor.internals.assignment.StreamsAssignmentProtocolVersions.LATEST_SUPPORTED_VERSION; | ||
| import static org.apache.kafka.streams.processor.internals.assignment.StreamsAssignmentProtocolVersions.UNKNOWN; | ||
|
|
@@ -630,12 +633,13 @@ private void checkAllPartitions(final Set<String> allSourceTopics, | |
| } | ||
|
|
||
| /** | ||
| * Resolve changelog topic metadata and create them if necessary. Fills in the changelogsByStatefulTask map | ||
| * and returns the set of changelogs which were newly created. | ||
| * Resolve changelog topic metadata and create them if necessary. Fills in the changelogsByStatefulTask map and | ||
| * the optimizedSourceChangelogs set and returns the set of changelogs which were newly created. | ||
| */ | ||
| private Set<String> prepareChangelogTopics(final Map<Integer, TopicsInfo> topicGroups, | ||
| final Map<Integer, Set<TaskId>> tasksForTopicGroup, | ||
| final Map<TaskId, Set<TopicPartition>> changelogsByStatefulTask) { | ||
| final Map<TaskId, Set<TopicPartition>> changelogsByStatefulTask, | ||
| final Set<String> optimizedSourceChangelogs) { | ||
| // add tasks to state change log topic subscribers | ||
| final Map<String, InternalTopicConfig> changelogTopicMetadata = new HashMap<>(); | ||
| for (final Map.Entry<Integer, TopicsInfo> entry : topicGroups.entrySet()) { | ||
|
|
@@ -671,6 +675,8 @@ private Set<String> prepareChangelogTopics(final Map<Integer, TopicsInfo> topicG | |
| topicConfig.setNumberOfPartitions(numPartitions); | ||
| changelogTopicMetadata.put(topicConfig.name(), topicConfig); | ||
| } | ||
|
|
||
| optimizedSourceChangelogs.addAll(topicsInfo.sourceTopicChangelogs()); | ||
| } | ||
|
|
||
| final Set<String> newlyCreatedTopics = internalTopicManager.makeReady(changelogTopicMetadata); | ||
|
|
@@ -692,11 +698,19 @@ private boolean assignTasksToClients(final Set<String> allSourceTopics, | |
| populateTasksForMaps(taskForPartition, tasksForTopicGroup, allSourceTopics, partitionsForTask, fullMetadata); | ||
|
|
||
| final Map<TaskId, Set<TopicPartition>> changelogsByStatefulTask = new HashMap<>(); | ||
| final Set<String> newlyCreatedChangelogs = prepareChangelogTopics(topicGroups, tasksForTopicGroup, changelogsByStatefulTask); | ||
| final Set<String> optimizedSourceChangelogs = new HashSet<>(); | ||
| final Set<String> newlyCreatedChangelogs = | ||
| prepareChangelogTopics(topicGroups, tasksForTopicGroup, changelogsByStatefulTask, optimizedSourceChangelogs); | ||
|
|
||
| final Map<UUID, ClientState> clientStates = new HashMap<>(); | ||
| final boolean lagComputationSuccessful = | ||
| populateClientStatesMap(clientStates, clientMetadataMap, taskForPartition, changelogsByStatefulTask, newlyCreatedChangelogs); | ||
| populateClientStatesMap(clientStates, | ||
| clientMetadataMap, | ||
| taskForPartition, | ||
| changelogsByStatefulTask, | ||
| newlyCreatedChangelogs, | ||
| optimizedSourceChangelogs | ||
| ); | ||
|
|
||
| final Set<TaskId> allTasks = partitionsForTask.keySet(); | ||
| final Set<TaskId> statefulTasks = changelogsByStatefulTask.keySet(); | ||
|
|
@@ -747,7 +761,8 @@ private boolean populateClientStatesMap(final Map<UUID, ClientState> clientState | |
| final Map<UUID, ClientMetadata> clientMetadataMap, | ||
| final Map<TopicPartition, TaskId> taskForPartition, | ||
| final Map<TaskId, Set<TopicPartition>> changelogsByStatefulTask, | ||
| final Set<String> newlyCreatedChangelogs) { | ||
| final Set<String> newlyCreatedChangelogs, | ||
| final Set<String> optimizedSourceChangelogs) { | ||
| boolean fetchEndOffsetsSuccessful; | ||
| Map<TaskId, Long> allTaskEndOffsetSums; | ||
| try { | ||
|
|
@@ -756,18 +771,36 @@ private boolean populateClientStatesMap(final Map<UUID, ClientState> clientState | |
| .flatMap(Collection::stream) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| final Collection<TopicPartition> allPreexistingChangelogPartitions = new ArrayList<>(allChangelogPartitions); | ||
| allPreexistingChangelogPartitions.removeIf(partition -> newlyCreatedChangelogs.contains(partition.topic())); | ||
| final Set<TopicPartition> preexistingChangelogPartitions = new HashSet<>(); | ||
| final Set<TopicPartition> preexistingSourceChangelogPartitions = new HashSet<>(); | ||
| final Set<TopicPartition> newlyCreatedChangelogPartitions = new HashSet<>(); | ||
| for (final TopicPartition changelog : allChangelogPartitions) { | ||
| if (newlyCreatedChangelogs.contains(changelog.topic())) { | ||
| newlyCreatedChangelogPartitions.add(changelog); | ||
| } else if (optimizedSourceChangelogs.contains(changelog.topic())) { | ||
| preexistingSourceChangelogPartitions.add(changelog); | ||
| } else { | ||
| preexistingChangelogPartitions.add(changelog); | ||
| } | ||
| } | ||
|
|
||
| // Make the listOffsets request first so it can fetch the offsets for non-source changelogs | ||
| // asynchronously while we use the blocking Consumer#committed call to fetch source-changelog offsets | ||
| final KafkaFuture<Map<TopicPartition, ListOffsetsResultInfo>> endOffsetsFuture = | ||
| fetchEndOffsetsFuture(preexistingChangelogPartitions, adminClient); | ||
|
|
||
| final Collection<TopicPartition> allNewlyCreatedChangelogPartitions = new ArrayList<>(allChangelogPartitions); | ||
| allNewlyCreatedChangelogPartitions.removeAll(allPreexistingChangelogPartitions); | ||
| final Map<TopicPartition, Long> sourceChangelogEndOffsets = | ||
| fetchCommittedOffsets(preexistingSourceChangelogPartitions, taskManager.mainConsumer()); | ||
|
|
||
| final Map<TopicPartition, ListOffsetsResultInfo> endOffsets = | ||
| fetchEndOffsets(allPreexistingChangelogPartitions, adminClient); | ||
| final Map<TopicPartition, ListOffsetsResultInfo> endOffsets = ClientUtils.getEndOffsets(endOffsetsFuture); | ||
|
|
||
| allTaskEndOffsetSums = computeEndOffsetSumsByTask(endOffsets, changelogsByStatefulTask, allNewlyCreatedChangelogPartitions); | ||
| allTaskEndOffsetSums = computeEndOffsetSumsByTask( | ||
| changelogsByStatefulTask, | ||
| endOffsets, | ||
| sourceChangelogEndOffsets, | ||
| newlyCreatedChangelogPartitions); | ||
| fetchEndOffsetsSuccessful = true; | ||
| } catch (final StreamsException e) { | ||
| } catch (final StreamsException | TimeoutException e) { | ||
|
Member
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. @vvcephei I've been wondering if maybe we should only catch the TimeoutException, and interpret a StreamsException as fatal (like IllegalStateException for example). This is how we were using
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. That sounds reasonable, but I think if you throw an exception in the assignor, it just calls the assignor again in a tight loop, which seems worse than backing off and trying again later. If you want to propose this change, maybe you can verify what exactly happens if we throw.
Member
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.
Wouldn't the leader thread just die? Not saying that that's ideal, either. But it's at least in line with how exceptions thrown by other admin client requests in the assignment are currently handled. |
||
| allTaskEndOffsetSums = changelogsByStatefulTask.keySet().stream().collect(Collectors.toMap(t -> t, t -> UNKNOWN_OFFSET_SUM)); | ||
| fetchEndOffsetsSuccessful = false; | ||
| } | ||
|
|
@@ -784,13 +817,16 @@ private boolean populateClientStatesMap(final Map<UUID, ClientState> clientState | |
| } | ||
|
|
||
| /** | ||
| * @param endOffsets the listOffsets result from the adminClient, or null if the request failed | ||
| * @param changelogsByStatefulTask map from stateful task to its set of changelog topic partitions | ||
| * @param endOffsets the listOffsets result from the adminClient | ||
| * @param sourceChangelogEndOffsets the end (committed) offsets of optimized source changelogs | ||
| * @param newlyCreatedChangelogPartitions any changelogs that were just created duringthis assignment | ||
| * | ||
| * @return Map from stateful task to its total end offset summed across all changelog partitions | ||
| */ | ||
| private Map<TaskId, Long> computeEndOffsetSumsByTask(final Map<TopicPartition, ListOffsetsResultInfo> endOffsets, | ||
| final Map<TaskId, Set<TopicPartition>> changelogsByStatefulTask, | ||
| private Map<TaskId, Long> computeEndOffsetSumsByTask(final Map<TaskId, Set<TopicPartition>> changelogsByStatefulTask, | ||
| final Map<TopicPartition, ListOffsetsResultInfo> endOffsets, | ||
| final Map<TopicPartition, Long> sourceChangelogEndOffsets, | ||
| final Collection<TopicPartition> newlyCreatedChangelogPartitions) { | ||
| final Map<TaskId, Long> taskEndOffsetSums = new HashMap<>(); | ||
| for (final Map.Entry<TaskId, Set<TopicPartition>> taskEntry : changelogsByStatefulTask.entrySet()) { | ||
|
|
@@ -802,13 +838,13 @@ private Map<TaskId, Long> computeEndOffsetSumsByTask(final Map<TopicPartition, L | |
| final long changelogEndOffset; | ||
| if (newlyCreatedChangelogPartitions.contains(changelog)) { | ||
| changelogEndOffset = 0L; | ||
| } else if (sourceChangelogEndOffsets.containsKey(changelog)) { | ||
| changelogEndOffset = sourceChangelogEndOffsets.get(changelog); | ||
| } else if (endOffsets.containsKey(changelog)) { | ||
| changelogEndOffset = endOffsets.get(changelog).offset(); | ||
| } else { | ||
| final ListOffsetsResultInfo offsetResult = endOffsets.get(changelog); | ||
| if (offsetResult == null) { | ||
| log.debug("Fetched end offsets did not contain the changelog {} of task {}", changelog, task); | ||
| throw new IllegalStateException("Could not get end offset for " + changelog); | ||
| } | ||
| changelogEndOffset = offsetResult.offset(); | ||
| log.debug("Fetched offsets did not contain the changelog {} of task {}", changelog, task); | ||
| throw new IllegalStateException("Could not get end offset for " + changelog); | ||
| } | ||
| final long newEndOffsetSum = taskEndOffsetSums.get(task) + changelogEndOffset; | ||
| if (newEndOffsetSum < 0) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.