-
Notifications
You must be signed in to change notification settings - Fork 14.1k
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
KAFKA-6145: Pt 1. Bump protocol version and encode task lag map #8121
Changes from 20 commits
5a67b59
8b45f78
b4d91a8
2aabf29
f510057
c063dc4
424af9a
cac1f39
72ecf58
c2da9f7
a37a8eb
40e20e3
7d8bdc2
e4e941f
0fe088a
361e449
951c245
41d70a2
aeebfa8
16be046
cce6847
36b94cc
f0f2022
9c1849c
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 |
---|---|---|
|
@@ -51,6 +51,7 @@ | |
|
||
import static org.apache.kafka.streams.processor.internals.Task.State.CREATED; | ||
import static org.apache.kafka.streams.processor.internals.Task.State.RESTORING; | ||
import static org.apache.kafka.streams.processor.internals.Task.State.RUNNING; | ||
|
||
public class TaskManager { | ||
// initialize the task list | ||
|
@@ -354,11 +355,27 @@ void handleLostAll() { | |
} | ||
} | ||
|
||
/** | ||
* @return Map from task id to its total offset summed across all state stores | ||
*/ | ||
public Map<TaskId, Long> getTaskOffsetSums() { | ||
final Map<TaskId, Long> taskOffsetSums = new HashMap<>(); | ||
|
||
for (final TaskId id : tasksOnLocalStorage()) { | ||
if (isRunning(id)) { | ||
taskOffsetSums.put(id, Task.LATEST_OFFSET); | ||
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'm just a tiny bit uncomfortable with re-using that sentinel, because the correctness of our logic depends on the active sentinel being less than the standby sentinel, so it must be less than zero. Do we have a reason to believe that 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 actually changed this based on working on the next PR, as |
||
} else { | ||
taskOffsetSums.put(id, 0L); | ||
} | ||
} | ||
return taskOffsetSums; | ||
} | ||
|
||
/** | ||
* Returns ids of tasks whose states are kept on the local storage. This includes active, standby, and previously | ||
* assigned but not yet cleaned up tasks | ||
*/ | ||
public Set<TaskId> tasksOnLocalStorage() { | ||
private Set<TaskId> tasksOnLocalStorage() { | ||
// A client could contain some inactive tasks whose states are still kept on the local storage in the following scenarios: | ||
// 1) the client is actively maintaining standby tasks by maintaining their states from the change log. | ||
// 2) the client has just got some tasks migrated out of itself to other clients while these task states | ||
|
@@ -472,6 +489,11 @@ private Stream<Task> standbyTaskStream() { | |
return tasks.values().stream().filter(t -> !t.isActive()); | ||
} | ||
|
||
private boolean isRunning(final TaskId id) { | ||
final Task task = tasks.get(id); | ||
return task != null && task.isActive() && task.state() == RUNNING; | ||
} | ||
|
||
/** | ||
* @throws TaskMigratedException if committing offsets failed (non-EOS) | ||
* or if the task producer got fenced (EOS) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,11 +16,15 @@ | |
*/ | ||
package org.apache.kafka.streams.processor.internals.assignment; | ||
|
||
import java.util.HashSet; | ||
import java.util.Map; | ||
import org.apache.kafka.common.protocol.ByteBufferAccessor; | ||
import org.apache.kafka.common.protocol.ObjectSerializationCache; | ||
import org.apache.kafka.streams.errors.TaskAssignmentException; | ||
import org.apache.kafka.streams.internals.generated.SubscriptionInfoData; | ||
import org.apache.kafka.streams.internals.generated.SubscriptionInfoData.TaskOffsetSum; | ||
import org.apache.kafka.streams.processor.TaskId; | ||
import org.apache.kafka.streams.processor.internals.Task; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
|
@@ -41,6 +45,7 @@ public class SubscriptionInfo { | |
private final SubscriptionInfoData data; | ||
private Set<TaskId> prevTasksCache = null; | ||
private Set<TaskId> standbyTasksCache = null; | ||
private Map<TaskId, Long> taskOffsetSumsCache = null; | ||
|
||
static { | ||
// Just statically check to make sure that the generated code always stays in sync with the overall protocol | ||
|
@@ -69,12 +74,13 @@ private static void validateVersions(final int version, final int latestSupporte | |
public SubscriptionInfo(final int version, | ||
final int latestSupportedVersion, | ||
final UUID processId, | ||
final Set<TaskId> prevTasks, | ||
final Set<TaskId> standbyTasks, | ||
final String userEndPoint) { | ||
final String userEndPoint, | ||
final Map<TaskId, Long> taskOffsetSums) { | ||
validateVersions(version, latestSupportedVersion); | ||
final SubscriptionInfoData data = new SubscriptionInfoData(); | ||
data.setVersion(version); | ||
data.setProcessId(processId); | ||
|
||
if (version >= 2) { | ||
data.setUserEndPoint(userEndPoint == null | ||
? new byte[0] | ||
|
@@ -83,7 +89,38 @@ public SubscriptionInfo(final int version, | |
if (version >= 3) { | ||
data.setLatestSupportedVersion(latestSupportedVersion); | ||
} | ||
data.setProcessId(processId); | ||
if (version >= 7) { | ||
setTaskOffsetSumDataFromTaskOffsetSumMap(data, taskOffsetSums); | ||
} else { | ||
setPrevAndStandbySetsFromParsedTaskOffsetSumMap(data, taskOffsetSums); | ||
} | ||
this.data = data; | ||
} | ||
|
||
private static void setTaskOffsetSumDataFromTaskOffsetSumMap(final SubscriptionInfoData data, | ||
final Map<TaskId, Long> taskOffsetSums) { | ||
data.setTaskOffsetSums(taskOffsetSums.entrySet().stream().map(t -> { | ||
final SubscriptionInfoData.TaskOffsetSum taskOffsetSum = new SubscriptionInfoData.TaskOffsetSum(); | ||
taskOffsetSum.setTopicGroupId(t.getKey().topicGroupId); | ||
taskOffsetSum.setPartition(t.getKey().partition); | ||
taskOffsetSum.setOffsetSum(t.getValue()); | ||
return taskOffsetSum; | ||
}).collect(Collectors.toList())); | ||
} | ||
|
||
private static void setPrevAndStandbySetsFromParsedTaskOffsetSumMap(final SubscriptionInfoData data, | ||
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. Since we invoke this method from a number of places, should we add a flag and make sure it only sets the state once? 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. bump 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 realized the other callers actually don't need to call this at all, so now these only get called from the constructor |
||
final Map<TaskId, Long> taskOffsetSums) { | ||
final Set<TaskId> prevTasks = new HashSet<>(); | ||
final Set<TaskId> standbyTasks = new HashSet<>(); | ||
|
||
for (final Map.Entry<TaskId, Long> taskOffsetSum : taskOffsetSums.entrySet()) { | ||
if (taskOffsetSum.getValue() == Task.LATEST_OFFSET) { | ||
prevTasks.add(taskOffsetSum.getKey()); | ||
} else { | ||
standbyTasks.add(taskOffsetSum.getKey()); | ||
} | ||
} | ||
|
||
data.setPrevTasks(prevTasks.stream().map(t -> { | ||
final SubscriptionInfoData.TaskId taskId = new SubscriptionInfoData.TaskId(); | ||
taskId.setTopicGroupId(t.topicGroupId); | ||
|
@@ -96,8 +133,6 @@ public SubscriptionInfo(final int version, | |
taskId.setPartition(t.partition); | ||
return taskId; | ||
}).collect(Collectors.toList())); | ||
|
||
this.data = data; | ||
} | ||
|
||
private SubscriptionInfo(final SubscriptionInfoData subscriptionInfoData) { | ||
|
@@ -119,6 +154,10 @@ public UUID processId() { | |
|
||
public Set<TaskId> prevTasks() { | ||
if (prevTasksCache == null) { | ||
// lazily initialize the prev and standby task maps as they may not be needed | ||
if (data.version() >= 7) { | ||
setPrevAndStandbySetsFromParsedTaskOffsetSumMap(data, taskOffsetSums()); | ||
} | ||
prevTasksCache = Collections.unmodifiableSet( | ||
data.prevTasks() | ||
.stream() | ||
|
@@ -131,6 +170,10 @@ public Set<TaskId> prevTasks() { | |
|
||
public Set<TaskId> standbyTasks() { | ||
if (standbyTasksCache == null) { | ||
// lazily initialize the prev and standby task maps as they may not be needed | ||
if (data.version() >= 7) { | ||
setPrevAndStandbySetsFromParsedTaskOffsetSumMap(data, taskOffsetSums()); | ||
} | ||
standbyTasksCache = Collections.unmodifiableSet( | ||
data.standbyTasks() | ||
.stream() | ||
|
@@ -141,12 +184,39 @@ public Set<TaskId> standbyTasks() { | |
return standbyTasksCache; | ||
} | ||
|
||
public Map<TaskId, Long> taskOffsetSums() { | ||
if (taskOffsetSumsCache == null) { | ||
taskOffsetSumsCache = Collections.unmodifiableMap( | ||
data.taskOffsetSums() | ||
.stream() | ||
.collect(Collectors.toMap(t -> new TaskId(t.topicGroupId(), t.partition()), TaskOffsetSum::offsetSum)) | ||
); | ||
} | ||
return taskOffsetSumsCache; | ||
} | ||
|
||
public String userEndPoint() { | ||
return data.userEndPoint() == null || data.userEndPoint().length == 0 | ||
? null | ||
: new String(data.userEndPoint(), StandardCharsets.UTF_8); | ||
} | ||
|
||
public static Set<TaskId> getActiveTasksFromTaskOffsetSumMap(final Map<TaskId, Long> taskOffsetSums) { | ||
return taskOffsetSumMapToTaskSet(taskOffsetSums, true); | ||
} | ||
|
||
public static Set<TaskId> getStandbyTasksFromTaskOffsetSumMap(final Map<TaskId, Long> taskOffsetSums) { | ||
return taskOffsetSumMapToTaskSet(taskOffsetSums, false); | ||
} | ||
|
||
private static Set<TaskId> taskOffsetSumMapToTaskSet(final Map<TaskId, Long> taskOffsetSums, | ||
final boolean getActiveTasks) { | ||
return taskOffsetSums.entrySet().stream() | ||
.filter(t -> getActiveTasks == (t.getValue() == Task.LATEST_OFFSET)) | ||
.map(Map.Entry::getKey) | ||
.collect(Collectors.toSet()); | ||
} | ||
|
||
/** | ||
* @throws TaskAssignmentException if method fails to encode the data | ||
*/ | ||
|
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 plus the tech debt cleanup allows for the subscription handling to be greatly simplified, here and below in #assign