Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
public interface ChangelogReader extends ChangelogRegister {
/**
* Restore all registered state stores by reading from their changelogs
*
* @return the total number of records restored in this call
*/
void restore(final Map<TaskId, Task> tasks);
long restore(final Map<TaskId, Task> tasks);

/**
* Transit to restore active changelogs mode
Expand All @@ -41,6 +43,12 @@ public interface ChangelogReader extends ChangelogRegister {
*/
void transitToUpdateStandby();

/**
* @return true if the reader is in restoring active changelog mode;
* false if the reader is in updating standby changelog mode
*/
boolean isRestoringActive();

/**
* @return the changelog partitions that have been completed restoring
*/
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@ public void transitToUpdateStandby() {
state = ChangelogReaderState.STANDBY_UPDATING;
}

@Override
public boolean isRestoringActive() {
return state == ChangelogReaderState.ACTIVE_RESTORING;
}

/**
* Since it is shared for multiple tasks and hence multiple state managers, the registration would take its
* corresponding state manager as well for restoring.
Expand Down Expand Up @@ -423,49 +428,22 @@ public Set<TopicPartition> completedChangelogs() {
// 2. if all changelogs have finished, return early;
// 3. if there are any restoring changelogs, try to read from the restore consumer and process them.
@Override
public void restore(final Map<TaskId, Task> tasks) {

// If we are updating only standby tasks, and are not using a separate thread, we should
// use a non-blocking poll to unblock the processing as soon as possible.
final boolean useNonBlockingPoll = state == ChangelogReaderState.STANDBY_UPDATING && !stateUpdaterEnabled;

public long restore(final Map<TaskId, Task> tasks) {
initializeChangelogs(tasks, registeredChangelogs());

if (!activeRestoringChangelogs().isEmpty() && state == ChangelogReaderState.STANDBY_UPDATING) {
throw new IllegalStateException("Should not be in standby updating state if there are still un-completed active changelogs");
}

long totalRestored = 0L;
if (allChangelogsCompleted()) {
log.debug("Finished restoring all changelogs {}", changelogs.keySet());
return;
return totalRestored;
}

final Set<TopicPartition> restoringChangelogs = restoringChangelogs();
if (!restoringChangelogs.isEmpty()) {
final ConsumerRecords<byte[], byte[]> polledRecords;

try {
pauseResumePartitions(tasks, restoringChangelogs);

polledRecords = restoreConsumer.poll(useNonBlockingPoll ? Duration.ZERO : pollTime);

// TODO (?) If we cannot fetch records during restore, should we trigger `task.timeout.ms` ?
// TODO (?) If we cannot fetch records for standby task, should we trigger `task.timeout.ms` ?
} catch (final InvalidOffsetException e) {
log.warn("Encountered " + e.getClass().getName() +
" fetching records from restore consumer for partitions " + e.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" +
" it later.", e);

final Set<TaskId> corruptedTasks = new HashSet<>();
e.partitions().forEach(partition -> corruptedTasks.add(changelogs.get(partition).stateManager.taskId()));
throw new TaskCorruptedException(corruptedTasks, e);
} catch (final InterruptException interruptException) {
throw interruptException;
} catch (final KafkaException e) {
throw new StreamsException("Restore consumer get unexpected error polling records.", e);
}
final ConsumerRecords<byte[], byte[]> polledRecords = pollRecordsFromRestoreConsumer(tasks, restoringChangelogs);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to avoid checkstyle rules on func complexity, without much logical change (except letting restoreChangelog to return restored records count).


for (final TopicPartition partition : polledRecords.partitions()) {
bufferChangelogRecords(restoringChangelogByPartition(partition), polledRecords.records(partition));
Expand All @@ -479,12 +457,15 @@ public void restore(final Map<TaskId, Task> tasks) {
// small batches; this can be optimized in the future, e.g. wait longer for larger batches.
final TaskId taskId = changelogs.get(partition).stateManager.taskId();
try {
if (restoreChangelog(changelogs.get(partition))) {
final ChangelogMetadata changelogMetadata = changelogs.get(partition);
final int restored = restoreChangelog(changelogMetadata);
if (restored > 0 || changelogMetadata.state().equals(ChangelogState.COMPLETED)) {
final Task task = tasks.get(taskId);
if (task != null) {
task.clearTaskTimeout();
}
}
totalRestored += restored;
} catch (final TimeoutException timeoutException) {
tasks.get(taskId).maybeInitTaskTimeoutOrThrow(
time.milliseconds(),
Expand All @@ -497,6 +478,41 @@ public void restore(final Map<TaskId, Task> tasks) {

maybeLogRestorationProgress();
}

return totalRestored;
}

private ConsumerRecords<byte[], byte[]> pollRecordsFromRestoreConsumer(final Map<TaskId, Task> tasks,
final Set<TopicPartition> restoringChangelogs) {
// If we are updating only standby tasks, and are not using a separate thread, we should
// use a non-blocking poll to unblock the processing as soon as possible.
final boolean useNonBlockingPoll = state == ChangelogReaderState.STANDBY_UPDATING && !stateUpdaterEnabled;
final ConsumerRecords<byte[], byte[]> polledRecords;

try {
pauseResumePartitions(tasks, restoringChangelogs);

polledRecords = restoreConsumer.poll(useNonBlockingPoll ? Duration.ZERO : pollTime);

// TODO (?) If we cannot fetch records during restore, should we trigger `task.timeout.ms` ?
// TODO (?) If we cannot fetch records for standby task, should we trigger `task.timeout.ms` ?
} catch (final InvalidOffsetException e) {
log.warn("Encountered " + e.getClass().getName() +
" fetching records from restore consumer for partitions " + e.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 " +
"it later.", e);

final Set<TaskId> corruptedTasks = new HashSet<>();
e.partitions().forEach(partition -> corruptedTasks.add(changelogs.get(partition).stateManager.taskId()));
throw new TaskCorruptedException(corruptedTasks, e);
} catch (final InterruptException interruptException) {
throw interruptException;
} catch (final KafkaException e) {
throw new StreamsException("Restore consumer get unexpected error polling records.", e);
}

return polledRecords;
}

private void pauseResumePartitions(final Map<TaskId, Task> tasks,
Expand Down Expand Up @@ -623,19 +639,17 @@ private void bufferChangelogRecords(final ChangelogMetadata changelogMetadata, f
/**
* restore a changelog with its buffered records if there's any; for active changelogs also check if
* it has completed the restoration and can transit to COMPLETED state and trigger restore callbacks
*
* @return number of records restored
*/
private boolean restoreChangelog(final ChangelogMetadata changelogMetadata) {
private int restoreChangelog(final ChangelogMetadata changelogMetadata) {
final ProcessorStateManager stateManager = changelogMetadata.stateManager;
final StateStoreMetadata storeMetadata = changelogMetadata.storeMetadata;
final TopicPartition partition = storeMetadata.changelogPartition();
final String storeName = storeMetadata.store().name();
final int numRecords = changelogMetadata.bufferedLimitIndex;

boolean madeProgress = false;

if (numRecords != 0) {
madeProgress = true;

final List<ConsumerRecord<byte[], byte[]>> records = changelogMetadata.bufferedRecords.subList(0, numRecords);
stateManager.restore(storeMetadata, records);

Expand All @@ -650,7 +664,7 @@ private boolean restoreChangelog(final ChangelogMetadata changelogMetadata) {

final Long currentOffset = storeMetadata.offset();
log.trace("Restored {} records from changelog {} to store {}, end offset is {}, current offset is {}",
partition, storeName, numRecords, recordEndOffset(changelogMetadata.restoreEndOffset), currentOffset);
numRecords, partition, storeName, recordEndOffset(changelogMetadata.restoreEndOffset), currentOffset);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor logging fixes piggy-backed here.


changelogMetadata.bufferedLimitIndex = 0;
changelogMetadata.totalRestored += numRecords;
Expand All @@ -667,8 +681,6 @@ private boolean restoreChangelog(final ChangelogMetadata changelogMetadata) {

// we should check even if there's nothing restored, but do not check completed if we are processing standby tasks
if (changelogMetadata.stateManager.taskType() == Task.TaskType.ACTIVE && hasRestoredToEnd(changelogMetadata)) {
madeProgress = true;

log.info("Finished restoring changelog {} to store {} with a total number of {} records",
partition, storeName, changelogMetadata.totalRestored);

Expand All @@ -682,7 +694,7 @@ private boolean restoreChangelog(final ChangelogMetadata changelogMetadata) {
}
}

return madeProgress;
return numRecords;
}

private Set<Task> getTasksFromPartitions(final Map<TaskId, Task> tasks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ public static StreamThread create(final TopologyMetadata topologyMetadata,
topologyMetadata,
adminClient,
stateDirectory,
maybeCreateAndStartStateUpdater(stateUpdaterEnabled, config, changelogReader, topologyMetadata, time, clientId, threadIdx)
maybeCreateAndStartStateUpdater(stateUpdaterEnabled, streamsMetrics, config, changelogReader, topologyMetadata, time, clientId, threadIdx)
);
referenceContainer.taskManager = taskManager;

Expand Down Expand Up @@ -448,6 +448,7 @@ public static StreamThread create(final TopologyMetadata topologyMetadata,
}

private static StateUpdater maybeCreateAndStartStateUpdater(final boolean stateUpdaterEnabled,
final StreamsMetricsImpl streamsMetrics,
final StreamsConfig streamsConfig,
final ChangelogReader changelogReader,
final TopologyMetadata topologyMetadata,
Expand All @@ -456,7 +457,7 @@ private static StateUpdater maybeCreateAndStartStateUpdater(final boolean stateU
final int threadIdx) {
if (stateUpdaterEnabled) {
final String name = clientId + "-StateUpdater-" + threadIdx;
final StateUpdater stateUpdater = new DefaultStateUpdater(name, streamsConfig, changelogReader, topologyMetadata, time);
final StateUpdater stateUpdater = new DefaultStateUpdater(name, streamsMetrics.metricsRegistry(), streamsConfig, changelogReader, topologyMetadata, time);
stateUpdater.start();
return stateUpdater;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1552,7 +1552,9 @@ Map<TaskId, Task> allTasks() {
/**
* Returns tasks owned by the stream thread. With state updater disabled, these are all tasks. With
* state updater enabled, this does not return any tasks currently owned by the state updater.
* @return
*
* TODO: after we complete switching to state updater, we could rename this function as allRunningTasks
* to be differentiated from allTasks including running and restoring tasks
*/
Map<TaskId, Task> allOwnedTasks() {
// not bothering with an unmodifiable map, since the tasks themselves are mutable, but
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public int hashCode() {
public static final String OPERATIONS = " operations";
public static final String TOTAL_DESCRIPTION = "The total number of ";
public static final String RATE_DESCRIPTION = "The average per-second number of ";
public static final String RATIO_DESCRIPTION = "The fraction of time the thread spent on ";
public static final String AVG_LATENCY_DESCRIPTION = "The average latency of ";
public static final String MAX_LATENCY_DESCRIPTION = "The maximum latency of ";
public static final String RATE_DESCRIPTION_PREFIX = "The average number of ";
Expand Down Expand Up @@ -177,6 +178,10 @@ public Version version() {
return version;
}

public Metrics metricsRegistry() {
return metrics;
}

public RocksDBMetricsRecordingTrigger rocksDBMetricsRecordingTrigger() {
return rocksDBMetricsRecordingTrigger;
}
Expand Down
Loading