-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-9481: Graceful handling TaskMigrated and TaskCorrupted #8058
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
3bfd87c
7493782
33af581
4419900
23cd292
112d9c6
a4af5a5
64dad10
47f3e09
134dda3
2954571
e037be0
a8d81c0
bbd19bc
ae9457b
6b6c391
2fd2eae
f5ce3db
d444d84
40731e9
8738cf5
9831638
946a944
c7719c2
9210ceb
5f36288
6bbc2f6
f4049d0
03f4778
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 |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| * contributor license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright ownership. | ||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| * (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.apache.kafka.streams.errors; | ||
|
|
||
| import org.apache.kafka.common.TopicPartition; | ||
| import org.apache.kafka.streams.processor.TaskId; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * Indicates a specific task is corrupted and need to be re-initialized. It can be thrown when | ||
| * | ||
| * 1) Under EOS, if the checkpoint file does not contain offsets for corresponding store's changelogs, meaning | ||
| * previously it was not close cleanly; | ||
| * 2) Out-of-range exception thrown during restoration, meaning that the changelog has been modified and we re-bootstrap | ||
|
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. Just now having this thought... Supposing this happens, is it guaranteed to apply to all the stores in the task? I.e., do we really need to re-bootstrap all the stores, or just the one(s) for which our offset is out of range?
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. Yes, we can re-bootstrap the state stores only -- this is what we did in the past but that was a lot messier (remember we have to use the optional in fixed-order map? :P). My thoughts are that for non-EOS, the checkpoint file would likely exist so even re-bootstrap the whole task would be okay, for EOS, it is safer to re-bootstrap the whole task.
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's certainly safer, but the performance hit seems concerning... restoration i/o is already one of the things people complain about most, and this choice could amplify it multiple times over. Maybe we can handle it more cleanly by closing all the stores nicely, writing a checkpoint file with the out-of-range stores' checkpoints at 0, and then re-bootstrapping the task, so it only has to restore the broken stores?
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. That sounds a good idea. Let me try it out. |
||
| * the store. | ||
| */ | ||
| public class TaskCorruptedException extends StreamsException { | ||
|
|
||
| private final Map<TaskId, Set<TopicPartition>> taskWithChangelogs; | ||
|
|
||
| public TaskCorruptedException(final Map<TaskId, Set<TopicPartition>> taskWithChangelogs) { | ||
| super("Tasks with changelogs " + taskWithChangelogs + " are corrupted and hence needs to be re-initialized"); | ||
|
|
||
| this.taskWithChangelogs = taskWithChangelogs; | ||
| } | ||
|
|
||
| public Map<TaskId, Set<TopicPartition>> corruptedTaskWithChangelogs() { | ||
| return taskWithChangelogs; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,7 @@ | |
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static java.lang.String.format; | ||
|
|
@@ -84,11 +85,15 @@ public static class StateStoreMetadata { | |
| // update blindly with the given offset | ||
| private Long offset; | ||
|
|
||
| // corrupted state store should not be included in checkpointing | ||
| private boolean corrupted; | ||
|
|
||
| private StateStoreMetadata(final StateStore stateStore) { | ||
| this.stateStore = stateStore; | ||
| this.restoreCallback = null; | ||
| this.recordConverter = null; | ||
| this.changelogPartition = null; | ||
| this.corrupted = false; | ||
| this.offset = null; | ||
| } | ||
|
|
||
|
|
@@ -282,6 +287,20 @@ Collection<TopicPartition> changelogPartitions() { | |
| return changelogOffsets().keySet(); | ||
| } | ||
|
|
||
| void markChangelogAsCorrupted(final Set<TopicPartition> partitions) { | ||
| for (final StateStoreMetadata storeMetadata : stores.values()) { | ||
| if (partitions.contains(storeMetadata.changelogPartition)) { | ||
| storeMetadata.corrupted = true; | ||
| partitions.remove(storeMetadata.changelogPartition); | ||
| } | ||
| } | ||
|
|
||
| if (!partitions.isEmpty()) { | ||
| throw new IllegalStateException("Some partitions " + partitions + " are not contained in the store list of task " + | ||
| taskId + " marking as corrupted, this is not expected"); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public Map<TopicPartition, Long> changelogOffsets() { | ||
| // return the current offsets for those logged stores | ||
|
|
@@ -415,6 +434,8 @@ public void close() throws ProcessorStateException { | |
| log.error("Failed to close state store {}: ", store.name(), exception); | ||
| } | ||
| } | ||
|
|
||
| stores.clear(); | ||
|
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. We need to clear the stores map now since we may re-initialize the state stores upon reviving a task.
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. Do we also need to clear
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.
|
||
| } | ||
|
|
||
| if (firstException != null) { | ||
|
|
@@ -439,10 +460,11 @@ public void checkpoint(final Map<TopicPartition, Long> writtenOffsets) { | |
|
|
||
| final Map<TopicPartition, Long> checkpointingOffsets = new HashMap<>(); | ||
| for (final StateStoreMetadata storeMetadata : stores.values()) { | ||
| // store is logged, persistent, and has a valid current offset | ||
| // store is logged, persistent, not corrupted, and has a valid current offset | ||
| if (storeMetadata.changelogPartition != null && | ||
| storeMetadata.stateStore.persistent() && | ||
| storeMetadata.offset != null) { | ||
| storeMetadata.offset != null && | ||
| !storeMetadata.corrupted) { | ||
| checkpointingOffsets.put(storeMetadata.changelogPartition, storeMetadata.offset); | ||
| } | ||
| } | ||
|
|
||
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 case 1) would be done in another PR, I just added the java-doc here to complete the scope.