Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ public int hashCode() {
private static class ClientMetadata {

private final HostInfo hostInfo;
private final SortedSet<String> consumers;
private final ClientState state;
private final SortedSet<String> consumers;

ClientMetadata(final String endPoint) {

Expand Down Expand Up @@ -734,8 +734,10 @@ private boolean assignTasksToClients(final Cluster fullMetadata,
statefulTasks,
assignmentConfigs);

log.info("Assigned tasks to clients as {}{}.",
Utils.NL, clientStates.entrySet().stream().map(Map.Entry::toString).collect(Collectors.joining(Utils.NL)));
log.info("Assigned tasks {} including stateful {} to clients as: \n{}.",
allTasks, statefulTasks, clientStates.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue().currentAssignment())
.collect(Collectors.joining(Utils.NL)));

return probingRebalanceNeeded;
}
Expand Down Expand Up @@ -941,7 +943,9 @@ private Map<String, Assignment> computeNewAssignment(final Set<TaskId> statefulT
state
);

// Arbitrarily choose the leader's client to be responsible for triggering the probing rebalance
// Arbitrarily choose the leader's client to be responsible for triggering the probing rebalance,
// note once we pick the first consumer within the process to trigger probing rebalance, other consumer
// would not set to trigger any more.
final boolean encodeNextProbingRebalanceTime = shouldTriggerProbingRebalance && clientId.equals(taskManager.processId());

final boolean tasksRevoked = addClientAssignments(
Expand All @@ -963,6 +967,19 @@ private Map<String, Assignment> computeNewAssignment(final Set<TaskId> statefulT
rebalanceRequired = true;
log.debug("Requested client {} to schedule a followup rebalance", clientId);
}

log.info("Client {} per-consumer assignment:\n" +
"\tprev owned active {}\n" +
"\tprev owned standby {}\n" +
"\tassigned active {}\n" +
"\trevoking active {}" +
"\tassigned standby {}\n",
clientId,
clientMetadata.state.prevOwnedActiveTasksByConsumer(),
clientMetadata.state.prevOwnedStandbyByConsumer(),
clientMetadata.state.assignedActiveTasksByConsumer(),
clientMetadata.state.revokingActiveTasksByConsumer(),
clientMetadata.state.assignedStandbyTasksByConsumer());
}

if (rebalanceRequired) {
Expand Down Expand Up @@ -1036,14 +1053,15 @@ private boolean addClientAssignments(final Set<TaskId> statefulTasks,

if (!activeTasksRemovedPendingRevokation.isEmpty()) {
// TODO: once KAFKA-10078 is resolved we can leave it to the client to trigger this rebalance
log.info("Requesting followup rebalance be scheduled immediately due to tasks changing ownership.");
log.info("Requesting {} followup rebalance be scheduled immediately due to tasks changing ownership.", consumer);
info.setNextRebalanceTime(0L);
followupRebalanceRequiredForRevokedTasks = true;
// Don't bother to schedule a probing rebalance if an immediate one is already scheduled
shouldEncodeProbingRebalance = false;
} else if (shouldEncodeProbingRebalance) {
final long nextRebalanceTimeMs = time.milliseconds() + probingRebalanceIntervalMs();
log.info("Requesting followup rebalance be scheduled for {} ms to probe for caught-up replica tasks.", nextRebalanceTimeMs);
log.info("Requesting {} followup rebalance be scheduled for {} ms to probe for caught-up replica tasks.",
consumer, nextRebalanceTimeMs);
info.setNextRebalanceTime(nextRebalanceTimeMs);
shouldEncodeProbingRebalance = false;
}
Expand Down Expand Up @@ -1074,8 +1092,11 @@ private Set<TaskId> populateActiveTaskAndPartitionsLists(final List<TopicPartiti
final List<AssignedPartition> assignedPartitions = new ArrayList<>();
final Set<TaskId> removedActiveTasks = new TreeSet<>();

// Build up list of all assigned partition-task pairs
for (final TaskId taskId : activeTasksForConsumer) {
// Populate the consumer for assigned tasks without considering revocation,
// this is for debugging purposes only
clientState.assignActiveToConsumer(taskId, consumer);

final List<AssignedPartition> assignedPartitionsForTask = new ArrayList<>();
for (final TopicPartition partition : partitionsForTask.get(taskId)) {
final String oldOwner = clientState.previousOwnerForPartition(partition);
Expand All @@ -1091,6 +1112,8 @@ private Set<TaskId> populateActiveTaskAndPartitionsLists(final List<TopicPartiti
);
removedActiveTasks.add(taskId);

clientState.revokeActiveFromConsumer(taskId, consumer);
Comment thread
abbccdda marked this conversation as resolved.

// Clear the assigned partitions list for this task if any partition can not safely be assigned,
// so as not to encode a partial task
assignedPartitionsForTask.clear();
Expand Down Expand Up @@ -1126,19 +1149,23 @@ private Map<TaskId, Set<TopicPartition>> buildStandbyTaskMap(final String consum
final Map<TaskId, Set<TopicPartition>> partitionsForTask,
final ClientState clientState) {
final Map<TaskId, Set<TopicPartition>> standbyTaskMap = new HashMap<>();

for (final TaskId task : standbyTasks) {
clientState.assignStandbyToConsumer(task, consumer);
Comment thread
abbccdda marked this conversation as resolved.
standbyTaskMap.put(task, partitionsForTask.get(task));
}

for (final TaskId task : revokedTasks) {
if (allStatefulTasks.contains(task)) {
log.info("Adding removed stateful active task {} as a standby for {} before it is safely revoked in followup rebalance",
task, consumer
);
standbyTaskMap.put(task, partitionsForTask.get(task));
log.info("Adding removed stateful active task {} as a standby for {} before it is revoked in followup rebalance",
task, consumer);

// This has no effect on the assignment, as we'll never consult the ClientState again, but
// it does perform a useful assertion that the it's legal to assign this task as a standby to this instance
clientState.assignStandbyToConsumer(task, consumer);
clientState.assignStandby(task);

standbyTaskMap.put(task, partitionsForTask.get(task));
}
}
return standbyTaskMap;
Expand Down Expand Up @@ -1262,7 +1289,7 @@ static Map<String, List<TaskId>> assignTasksToThreads(final Collection<TaskId> s

private static SortedSet<TaskId> getPreviousTasksByLag(final ClientState state, final String consumer) {
final SortedSet<TaskId> prevTasksByLag = new TreeSet<>(comparingLong(state::lagFor).thenComparing(TaskId::compareTo));
prevTasksByLag.addAll(state.previousTasksForConsumer(consumer));
prevTasksByLag.addAll(state.prevOwnedStatefulTasksByConsumer(consumer));
return prevTasksByLag;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package org.apache.kafka.streams.processor.internals.assignment;

import java.util.stream.Collectors;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.streams.processor.TaskId;
import org.apache.kafka.streams.processor.internals.Task;
Expand All @@ -28,7 +27,7 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.stream.Collectors;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
Expand All @@ -44,15 +43,23 @@ public class ClientState {
private static final Logger LOG = LoggerFactory.getLogger(ClientState.class);
public static final Comparator<TopicPartition> TOPIC_PARTITION_COMPARATOR = comparing(TopicPartition::topic).thenComparing(TopicPartition::partition);

private final Set<TaskId> activeTasks;
private final Set<TaskId> standbyTasks;
private final Set<TaskId> activeTasks = new TreeSet<>();
private final Set<TaskId> standbyTasks = new TreeSet<>();
private final Set<TaskId> prevActiveTasks;
private final Set<TaskId> prevStandbyTasks;

private final Map<String, Set<TaskId>> consumerToPreviousStatefulTaskIds;
private final Map<TopicPartition, String> ownedPartitions;
private final Map<TaskId, Long> taskOffsetSums; // contains only stateful tasks we previously owned
private final Map<TaskId, Long> taskLagTotals; // contains lag for all stateful tasks in the app topology
private final Map<TopicPartition, String> ownedPartitions = new TreeMap<>(TOPIC_PARTITION_COMPARATOR);
private final Map<String, Set<TaskId>> consumerToPreviousStatefulTaskIds = new TreeMap<>();

// the following four maps are used only for logging purposes;
// TODO KAFKA-10283: we could consider merging them with other book-keeping maps at client-levels
// so that they would not be inconsistent
private final Map<String, Set<TaskId>> consumerToPreviousActiveTaskIds = new TreeMap<>();
private final Map<String, Set<TaskId>> consumerToAssignedActiveTaskIds = new TreeMap<>();
private final Map<String, Set<TaskId>> consumerToAssignedStandbyTaskIds = new TreeMap<>();
private final Map<String, Set<TaskId>> consumerToRevokingActiveTaskIds = new TreeMap<>();

private int capacity;

Expand All @@ -61,67 +68,25 @@ public ClientState() {
}

ClientState(final int capacity) {
activeTasks = new TreeSet<>();
standbyTasks = new TreeSet<>();
prevActiveTasks = new TreeSet<>();
prevStandbyTasks = new TreeSet<>();
consumerToPreviousStatefulTaskIds = new TreeMap<>();
ownedPartitions = new TreeMap<>(TOPIC_PARTITION_COMPARATOR);
taskOffsetSums = new TreeMap<>();
taskLagTotals = new TreeMap<>();
this.capacity = capacity;
}

private ClientState(final Set<TaskId> activeTasks,
Comment thread
abbccdda marked this conversation as resolved.
final Set<TaskId> standbyTasks,
final Set<TaskId> prevActiveTasks,
final Set<TaskId> prevStandbyTasks,
final Map<String, Set<TaskId>> consumerToPreviousStatefulTaskIds,
final SortedMap<TopicPartition, String> ownedPartitions,
final Map<TaskId, Long> taskOffsetSums,
final Map<TaskId, Long> taskLagTotals,
final int capacity) {
this.activeTasks = activeTasks;
this.standbyTasks = standbyTasks;
this.prevActiveTasks = prevActiveTasks;
this.prevStandbyTasks = prevStandbyTasks;
this.consumerToPreviousStatefulTaskIds = consumerToPreviousStatefulTaskIds;
this.ownedPartitions = ownedPartitions;
this.taskOffsetSums = taskOffsetSums;
this.taskLagTotals = taskLagTotals;
this.capacity = capacity;
}

// For testing only
public ClientState(final Set<TaskId> previousActiveTasks,
final Set<TaskId> previousStandbyTasks,
final Map<TaskId, Long> taskLagTotals,
final int capacity) {
activeTasks = new TreeSet<>();
standbyTasks = new TreeSet<>();
prevActiveTasks = unmodifiableSet(new TreeSet<>(previousActiveTasks));
prevStandbyTasks = unmodifiableSet(new TreeSet<>(previousStandbyTasks));
consumerToPreviousStatefulTaskIds = new TreeMap<>();
ownedPartitions = new TreeMap<>(TOPIC_PARTITION_COMPARATOR);
taskOffsetSums = emptyMap();
this.taskLagTotals = unmodifiableMap(taskLagTotals);
this.capacity = capacity;
}

public ClientState copy() {
final TreeMap<TopicPartition, String> newOwnedPartitions = new TreeMap<>(TOPIC_PARTITION_COMPARATOR);
newOwnedPartitions.putAll(ownedPartitions);
return new ClientState(
new TreeSet<>(activeTasks),
new TreeSet<>(standbyTasks),
new TreeSet<>(prevActiveTasks),
new TreeSet<>(prevStandbyTasks),
new TreeMap<>(consumerToPreviousStatefulTaskIds),
newOwnedPartitions,
new TreeMap<>(taskOffsetSums),
new TreeMap<>(taskLagTotals),
capacity);
}

int capacity() {
return capacity;
}
Expand Down Expand Up @@ -150,6 +115,53 @@ public void assignActiveTasks(final Collection<TaskId> tasks) {
activeTasks.addAll(tasks);
}

public void assignActiveToConsumer(final TaskId task, final String consumer) {
consumerToAssignedActiveTaskIds.computeIfAbsent(consumer, k -> new HashSet<>()).add(task);
}

public void assignStandbyToConsumer(final TaskId task, final String consumer) {
consumerToAssignedStandbyTaskIds.computeIfAbsent(consumer, k -> new HashSet<>()).add(task);
}

public void revokeActiveFromConsumer(final TaskId task, final String consumer) {
consumerToRevokingActiveTaskIds.computeIfAbsent(consumer, k -> new HashSet<>()).add(task);
}

public Map<String, Set<TaskId>> prevOwnedActiveTasksByConsumer() {
return consumerToPreviousActiveTaskIds;
}

public Map<String, Set<TaskId>> prevOwnedStandbyByConsumer() {
// standbys are just those stateful tasks minus active tasks
final Map<String, Set<TaskId>> consumerToPreviousStandbyTaskIds = new TreeMap<>();

for (final Map.Entry<String, Set<TaskId>> entry: consumerToPreviousStatefulTaskIds.entrySet()) {
final Set<TaskId> standbyTaskIds = new HashSet<>(entry.getValue());
if (consumerToPreviousActiveTaskIds.containsKey(entry.getKey()))
standbyTaskIds.removeAll(consumerToPreviousActiveTaskIds.get(entry.getKey()));
consumerToPreviousStandbyTaskIds.put(entry.getKey(), standbyTaskIds);
}

return consumerToPreviousStandbyTaskIds;
}

// including both active and standby tasks
public Set<TaskId> prevOwnedStatefulTasksByConsumer(final String memberId) {
return consumerToPreviousStatefulTaskIds.get(memberId);
}

public Map<String, Set<TaskId>> assignedActiveTasksByConsumer() {
return consumerToAssignedActiveTaskIds;
}

public Map<String, Set<TaskId>> revokingActiveTasksByConsumer() {
return consumerToRevokingActiveTaskIds;
}

public Map<String, Set<TaskId>> assignedStandbyTasksByConsumer() {
return consumerToAssignedStandbyTaskIds;
}

public void assignActive(final TaskId task) {
assertNotAssigned(task);
activeTasks.add(task);
Expand Down Expand Up @@ -316,10 +328,6 @@ public Set<TaskId> statelessActiveTasks() {
return activeTasks.stream().filter(task -> !isStateful(task)).collect(Collectors.toSet());
}

public Set<TaskId> previousTasksForConsumer(final String memberId) {
return consumerToPreviousStatefulTaskIds.get(memberId);
}

boolean hasUnfulfilledQuota(final int tasksPerThread) {
return activeTasks.size() < capacity * tasksPerThread;
}
Expand All @@ -345,13 +353,17 @@ boolean hasMoreAvailableCapacityThan(final ClientState other) {
}
}

public String currentAssignment() {
return "[activeTasks: (" + activeTasks +
") standbyTasks: (" + standbyTasks + ")]";
}

@Override
public String toString() {
return "[activeTasks: (" + activeTasks +
") standbyTasks: (" + standbyTasks +
") prevActiveTasks: (" + prevActiveTasks +
") prevStandbyTasks: (" + prevStandbyTasks +
") prevOwnedPartitionsByConsumerId: (" + ownedPartitions.keySet() +
") changelogOffsetTotalsByTask: (" + taskOffsetSums.entrySet() +
") taskLagTotals: (" + taskLagTotals.entrySet() +
") capacity: " + capacity +
Expand All @@ -373,6 +385,7 @@ private void initializePrevActiveTasksFromOwnedPartitions(final Map<TopicPartiti
final TaskId task = taskForPartitionMap.get(tp);
if (task != null) {
addPreviousActiveTask(task);
consumerToPreviousActiveTaskIds.computeIfAbsent(partitionEntry.getValue(), k -> new HashSet<>()).add(task);
} else {
LOG.error("No task found for topic partition {}", tp);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ public final class AssignmentTestUtils {
public static final UUID UUID_5 = uuidForInt(5);
public static final UUID UUID_6 = uuidForInt(6);

public static final TopicPartition TP_0_0 = new TopicPartition("topic0", 0);
public static final TopicPartition TP_0_1 = new TopicPartition("topic0", 1);
public static final TopicPartition TP_0_2 = new TopicPartition("topic0", 2);
public static final TopicPartition TP_1_0 = new TopicPartition("topic1", 0);
public static final TopicPartition TP_1_1 = new TopicPartition("topic1", 1);
public static final TopicPartition TP_1_2 = new TopicPartition("topic1", 2);

public static final TaskId TASK_0_0 = new TaskId(0, 0);
public static final TaskId TASK_0_1 = new TaskId(0, 1);
public static final TaskId TASK_0_2 = new TaskId(0, 2);
Expand Down
Loading