-
Notifications
You must be signed in to change notification settings - Fork 15.4k
[KAFKA-6730] Simplify State Store Recovery #5013
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 7 commits
9b41afa
a129b4e
4883a43
804a850
3963257
78d81c3
8207dbe
303fbab
4a33d9c
dcb5f0c
e742d31
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 |
|---|---|---|
|
|
@@ -26,7 +26,6 @@ | |
| import org.apache.kafka.common.utils.LogContext; | ||
| import org.apache.kafka.streams.KeyValue; | ||
| import org.apache.kafka.streams.errors.StreamsException; | ||
| import org.apache.kafka.streams.errors.TaskMigratedException; | ||
| import org.apache.kafka.streams.processor.StateRestoreListener; | ||
| import org.slf4j.Logger; | ||
|
|
||
|
|
@@ -50,6 +49,7 @@ public class StoreChangelogReader implements ChangelogReader { | |
| private final Map<TopicPartition, StateRestorer> stateRestorers = new HashMap<>(); | ||
| private final Map<TopicPartition, StateRestorer> needsRestoring = new HashMap<>(); | ||
| private final Map<TopicPartition, StateRestorer> needsInitializing = new HashMap<>(); | ||
| private Map<TopicPartition, Long> updatedEndOffsets = new HashMap<>(); | ||
|
|
||
| public StoreChangelogReader(final Consumer<byte[], byte[]> restoreConsumer, | ||
| final StateRestoreListener userStateRestoreListener, | ||
|
|
@@ -66,9 +66,6 @@ public void register(final StateRestorer restorer) { | |
| needsInitializing.put(restorer.partition(), restorer); | ||
| } | ||
|
|
||
| /** | ||
| * @throws TaskMigratedException if another thread wrote to the changelog topic that is currently restored | ||
| */ | ||
| public Collection<TopicPartition> restore(final RestoringTasks active) { | ||
| if (!needsInitializing.isEmpty()) { | ||
| initialize(); | ||
|
|
@@ -81,10 +78,25 @@ public Collection<TopicPartition> restore(final RestoringTasks active) { | |
|
|
||
| final Set<TopicPartition> restoringPartitions = new HashSet<>(needsRestoring.keySet()); | ||
| try { | ||
| final ConsumerRecords<byte[], byte[]> allRecords = restoreConsumer.poll(10); | ||
| for (final TopicPartition partition : restoringPartitions) { | ||
| restorePartition(allRecords, partition, active.restoringTaskFor(partition)); | ||
| final Set<TopicPartition> remainingPartitions = new HashSet<>(needsRestoring.keySet()); | ||
| remainingPartitions.removeAll(updatedEndOffsets.keySet()); | ||
| updatedEndOffsets.putAll(restoreConsumer.endOffsets(remainingPartitions)); | ||
|
Member
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. This seems to be correct. Still wondering why you did not move this code into I think that
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. Sorry about my delay. It should be fine now.
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. @mjsax Actually, needsInitializing can still be empty, but remainingPartitions would still have partitions which needs to be added from needsRestoring. I think the main reason is that needsInitializing and needsRestoring are completely independent of one another. If one does not contain partitions, that does not neccesarily effect the other. In other words, as long as needsRestoring has partitions which needs to be restored, we will probably need to check for partitions every time -- regardless of what needsInitializing contains.
Member
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 cannot follow. Not sure what I am missing. From my understanding the flow is as follows: (1) after new partitions are assigned, they are added to We need to maintain
Why? Those partitions should have been added to
From my understanding, they are not independent. Each partitions is first in
Why? The end-offset should not change and thus, for each partition that is moved from Please let me know what I miss.
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. Oh, I see what happened. Sorry, this was my bad. What happened was that I was adding the end offsets to |
||
| final ConsumerRecords<byte[], byte[]> records = restoreConsumer.poll(10); | ||
| final Iterator<TopicPartition> iterator = restoringPartitions.iterator(); | ||
|
Member
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. replace |
||
| final Set<TopicPartition> completedPartitions = new HashSet<>(); | ||
| while (iterator.hasNext()) { | ||
| final TopicPartition partition = iterator.next(); | ||
| final StateRestorer restorer = stateRestorers.get(partition); | ||
| final long pos = processNext(records.records(partition), restorer, updatedEndOffsets.get(partition)); | ||
| restorer.setRestoredOffset(pos); | ||
| if (restorer.hasCompleted(pos, updatedEndOffsets.get(partition))) { | ||
|
Member
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. we can also remove the entry in |
||
| restorer.restoreDone(); | ||
| needsRestoring.remove(partition); | ||
| updatedEndOffsets.remove(partition); | ||
| completedPartitions.add(partition); | ||
|
Member
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. One more thing -- missed this before: can't we remove Maybe we need to change the iterator to iterate over the |
||
| } | ||
| } | ||
| restoringPartitions.removeAll(completedPartitions); | ||
| } catch (final InvalidOffsetException recoverableException) { | ||
| log.warn("Restoring StreamTasks failed. Deleting StreamTasks stores to recreate from scratch.", recoverableException); | ||
| final Set<TopicPartition> partitions = recoverableException.partitions(); | ||
|
|
@@ -240,41 +252,6 @@ public void reset() { | |
| needsInitializing.clear(); | ||
| } | ||
|
|
||
| /** | ||
| * @throws TaskMigratedException if another thread wrote to the changelog topic that is currently restored | ||
| */ | ||
| private void restorePartition(final ConsumerRecords<byte[], byte[]> allRecords, | ||
| final TopicPartition topicPartition, | ||
| final Task task) { | ||
| final StateRestorer restorer = stateRestorers.get(topicPartition); | ||
| final Long endOffset = endOffsets.get(topicPartition); | ||
| final long pos = processNext(allRecords.records(topicPartition), restorer, endOffset); | ||
| restorer.setRestoredOffset(pos); | ||
| if (restorer.hasCompleted(pos, endOffset)) { | ||
| if (pos > endOffset) { | ||
| throw new TaskMigratedException(task, topicPartition, endOffset, pos); | ||
| } | ||
|
|
||
| // need to check for changelog topic | ||
| if (restorer.offsetLimit() == Long.MAX_VALUE) { | ||
| final Long updatedEndOffset = restoreConsumer.endOffsets(Collections.singletonList(topicPartition)).get(topicPartition); | ||
| if (!restorer.hasCompleted(pos, updatedEndOffset)) { | ||
| throw new TaskMigratedException(task, topicPartition, updatedEndOffset, pos); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| log.debug("Completed restoring state from changelog {} with {} records ranging from offset {} to {}", | ||
| topicPartition, | ||
| restorer.restoredNumRecords(), | ||
| restorer.startingOffset(), | ||
| restorer.restoredOffset()); | ||
|
|
||
| restorer.restoreDone(); | ||
| needsRestoring.remove(topicPartition); | ||
| } | ||
| } | ||
|
|
||
| private long processNext(final List<ConsumerRecord<byte[], byte[]>> records, | ||
| final StateRestorer restorer, | ||
| final Long endOffset) { | ||
|
|
@@ -326,7 +303,6 @@ private boolean hasPartition(final TopicPartition topicPartition) { | |
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
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 we can remove this variable to simplify the code further.