KAFKA-9481: Graceful handling TaskMigrated and TaskCorrupted (#8058)
1. Removed task field from TaskMigrated; the only caller that encodes a task id from StreamTask actually do not throw so we only log it. To handle it on StreamThread we just always enforce rebalance (and we would call onPartitionsLost to remove all tasks as dirty).
2. Added TaskCorruptedException with a set of task-ids. The first scenario of this is the restoreConsumer.poll which throws InvalidOffset indicating that the logs are truncated / compacted. To handle it on StreamThread we first close the corresponding tasks as dirty (if EOS is enabled we would also wipe out the state stores), and then revive them into the CREATED state.
3. Also fixed a bug while investigating KAFKA-9572: when suspending / closing a restoring task we should not commit the new offsets but only updating the checkpoint file.
4. Re-enabled the unit test.
@ -50,12 +50,6 @@ public final class FixedOrderMap<K, V> extends LinkedHashMap<K, V> {
@@ -50,12 +50,6 @@ public final class FixedOrderMap<K, V> extends LinkedHashMap<K, V> {
thrownewUnsupportedOperationException("Removing from registeredStores is not allowed");
}
@Deprecated
@Override
publicvoidclear(){
thrownewUnsupportedOperationException("Removing from registeredStores is not allowed");
@ -84,11 +85,15 @@ public class ProcessorStateManager implements StateManager {
@@ -84,11 +85,15 @@ public class ProcessorStateManager implements StateManager {
// update blindly with the given offset
privateLongoffset;
// corrupted state store should not be included in checkpointing
@ -282,6 +287,20 @@ public class ProcessorStateManager implements StateManager {
@@ -282,6 +287,20 @@ public class ProcessorStateManager implements StateManager {
thrownewIllegalStateException("Some partitions "+partitions+" are not contained in the store list of task "+
taskId+" marking as corrupted, this is not expected");
}
}
@Override
publicMap<TopicPartition,Long>changelogOffsets(){
// return the current offsets for those logged stores
@ -415,6 +434,8 @@ public class ProcessorStateManager implements StateManager {
@@ -415,6 +434,8 @@ public class ProcessorStateManager implements StateManager {
log.error("Failed to close state store {}: ",store.name(),exception);
}
}
stores.clear();
}
if(firstException!=null){
@ -439,10 +460,11 @@ public class ProcessorStateManager implements StateManager {
@@ -439,10 +460,11 @@ public class ProcessorStateManager implements StateManager {
@ -132,7 +132,7 @@ public class RecordCollectorImpl implements RecordCollector {
@@ -132,7 +132,7 @@ public class RecordCollectorImpl implements RecordCollector {
try{
producer.beginTransaction();
}catch(finalProducerFencedExceptionerror){
thrownewTaskMigratedException(taskId,"Producer get fenced trying to begin a new transaction",error);
thrownewTaskMigratedException("Producer get fenced trying to begin a new transaction",error);
}catch(finalKafkaExceptionerror){
thrownewStreamsException("Producer encounter unexpected error trying to begin a new transaction",error);
}
@ -169,7 +169,7 @@ public class RecordCollectorImpl implements RecordCollector {
@@ -169,7 +169,7 @@ public class RecordCollectorImpl implements RecordCollector {
producer.commitTransaction();
transactionInFlight=false;
}catch(finalProducerFencedExceptionerror){
thrownewTaskMigratedException(taskId,"Producer get fenced trying to commit a transaction",error);
thrownewTaskMigratedException("Producer get fenced trying to commit a transaction",error);
}catch(finalTimeoutExceptionerror){
// TODO KIP-447: we can consider treating it as non-fatal and retry on the thread level
thrownewStreamsException("Timed out while committing transaction via producer for task "+taskId,error);
@ -181,7 +181,7 @@ public class RecordCollectorImpl implements RecordCollector {
@@ -181,7 +181,7 @@ public class RecordCollectorImpl implements RecordCollector {
"indicating the corresponding thread is no longer part of the group.",error);
}catch(finalTimeoutExceptionerror){
// TODO KIP-447: we can consider treating it as non-fatal and retry on the thread level
@ -216,7 +216,7 @@ public class RecordCollectorImpl implements RecordCollector {
@@ -216,7 +216,7 @@ public class RecordCollectorImpl implements RecordCollector {
errorMessage+="\nThe broker is either slow or in bad state (like not having enough replicas) in responding the request, "+
@ -314,7 +314,7 @@ public class RecordCollectorImpl implements RecordCollector {
@@ -314,7 +314,7 @@ public class RecordCollectorImpl implements RecordCollector {
if(isRecoverable(uncaughtException)){
// producer.send() call may throw a KafkaException which wraps a FencedException,
// in this case we should throw its wrapped inner cause so that it can be captured and re-wrapped as TaskMigrationException
thrownewTaskMigratedException(taskId,"Producer cannot send records anymore since it got fenced",uncaughtException.getCause());
thrownewTaskMigratedException("Producer cannot send records anymore since it got fenced",uncaughtException.getCause());
@ -98,10 +100,6 @@ public class StoreChangelogReader implements ChangelogReader {
@@ -98,10 +100,6 @@ public class StoreChangelogReader implements ChangelogReader {
privatelongtotalRestored;
// only for active restoring tasks (for standby changelog it is null)
// NOTE we do not book keep the current offset since we leverage state manager as its source of truth
// the end offset beyond which records should not be applied (yet) to restore the states
//
// for both active restoring tasks and standby updating tasks, it is defined as:
@ -111,6 +109,8 @@ public class StoreChangelogReader implements ChangelogReader {
@@ -111,6 +109,8 @@ public class StoreChangelogReader implements ChangelogReader {
// the log-end-offset only needs to be updated once and only need to be for active tasks since for standby
// tasks it would never "complete" based on the end-offset;
// the committed-offset needs to be updated periodically for those standby tasks
//
// NOTE we do not book keep the current offset since we leverage state manager as its source of truth
privateLongrestoreEndOffset;
// buffer records polled by the restore consumer;
@ -258,6 +258,8 @@ public class StoreChangelogReader implements ChangelogReader {
@@ -258,6 +258,8 @@ public class StoreChangelogReader implements ChangelogReader {
// if we cannot get the position of the consumer within timeout, just return false
returnfalse;
}catch(finalKafkaExceptione){
// this also includes InvalidOffsetException, which should not happen under normal
// execution, hence it is also okay to wrap it as fatal StreamsException
thrownewStreamsException("Restore consumer get unexpected error trying to get the position "+
" of "+partition,e);
}
@ -411,6 +413,18 @@ public class StoreChangelogReader implements ChangelogReader {
@@ -411,6 +413,18 @@ public class StoreChangelogReader implements ChangelogReader {
}catch(finalFencedInstanceIdExceptione){
// when the consumer gets fenced, all its tasks should be migrated
thrownewTaskMigratedException("Restore consumer get fenced by instance-id polling records.",e);
}catch(finalInvalidOffsetExceptione){
log.warn("Encountered {} fetching records from restore consumer for partitions {}, it is likely that "+
"the consumer's position has fallen out of the topic partition offset range because the topic was "+
"truncated or compacted on the broker, marking the corresponding tasks as corrupted and re-initializing"+
thrownewStreamsException("Restore consumer get unexpected error polling records.",e);
}
@ -428,7 +442,6 @@ public class StoreChangelogReader implements ChangelogReader {
@@ -428,7 +442,6 @@ public class StoreChangelogReader implements ChangelogReader {
restoreChangelog(changelogs.get(partition));
}
maybeUpdateLimitOffsetsForStandbyChangelogs();
}
}
@ -681,6 +694,8 @@ public class StoreChangelogReader implements ChangelogReader {
@@ -681,6 +694,8 @@ public class StoreChangelogReader implements ChangelogReader {
}
assignment.addAll(partitions);
restoreConsumer.assign(assignment);
log.debug("Added partitions {} to the restore consumer, current assignment is {}",partitions,assignment);
@ -692,6 +707,8 @@ public class StoreChangelogReader implements ChangelogReader {
@@ -692,6 +707,8 @@ public class StoreChangelogReader implements ChangelogReader {
"does not contain some of the partitions "+partitions+" for pausing.");
}
restoreConsumer.pause(partitions);
log.debug("Paused partitions {} from the restore consumer",partitions);
@ -715,6 +732,8 @@ public class StoreChangelogReader implements ChangelogReader {
@@ -715,6 +732,8 @@ public class StoreChangelogReader implements ChangelogReader {
"does not contain some of the partitions "+partitions+" for resuming.");
}
restoreConsumer.resume(partitions);
log.debug("Resumed partitions {} from the restore consumer",partitions);
@ -761,6 +780,8 @@ public class StoreChangelogReader implements ChangelogReader {
@@ -761,6 +780,8 @@ public class StoreChangelogReader implements ChangelogReader {
}catch(finalTimeoutExceptione){
// if we cannot find the starting position at the beginning, just use the default 0L
}catch(finalKafkaExceptione){
// this also includes InvalidOffsetException, which should not happen under normal
// execution, hence it is also okay to wrap it as fatal StreamsException
thrownewStreamsException("Restore consumer get unexpected error trying to get the position "+
log.warn("Detected that the thread is being fenced. "+
"This implies that this thread missed a rebalance and dropped out of the consumer group. "+
"Will close out all assigned tasks and rejoin the consumer group.");
taskManager.handleLostAll();
enforceRebalance();
}
}
@ -849,7 +854,7 @@ public class StreamThread extends Thread {
@@ -849,7 +854,7 @@ public class StreamThread extends Thread {
*5.Ifoneoftheabovehappens,halfthevalueofN.
*/
intprocessed=0;
longtimeSinceLastPoll=0L;
longtimeSinceLastPoll;
do{
for(inti=0;i<numIterations;i++){
@ -946,7 +951,7 @@ public class StreamThread extends Thread {
@@ -946,7 +951,7 @@ public class StreamThread extends Thread {
if(originalReset.equals("earliest")){
addToResetList(partition,seekToBeginning,"No custom setting defined for topic '{}' using original config '{}' for offset reset","earliest",loggedTopics);
}elseif(originalReset.equals("latest")){
}else{
addToResetList(partition,seekToEnd,"No custom setting defined for topic '{}' using original config '{}' for offset reset","latest",loggedTopics);
}
}
@ -974,16 +979,12 @@ public class StreamThread extends Thread {
@@ -974,16 +979,12 @@ public class StreamThread extends Thread {