lucasbru commented on code in PR #23454:
URL: https://github.com/apache/kafka/pull/23454#discussion_r4034924120


##########
group-coordinator/src/main/java/org/apache/kafka/coordinator/group/streams/AssignmentRefinerImpl.java:
##########
@@ -288,6 +286,240 @@ private static boolean isReady(
             .anyMatch(holder -> holder.processId().equals(targetProcessId) && 
holder.caughtUp());
     }
 
+    /**
+     * Decides which of the staged migrations get a warm-up task, under the 
warmup budget.
+     *
+     * <p>There is different scenarios:
+     * <ul>
+     *     <li>A warm-up task already restoring keeps its warm-up slot if the 
target assignment didn't change, and the
+     *     warmu-up task is not caught up yet. It could also get revoked if 
the warmup budget was reduced and keeping
+     *     the warmup would now exceed the budget.
+     *     <li>A <b>fresh plant</b> puts a warm-up task on a target owner 
whose process holds nothing for the task,
+     *     and spends a warm-up slot.</li>
+     *     <li>When the target owner <em>itself</em> already holds a standby 
of the task we can <b>borrow</b> it,
+     *     and no warmup budget is used: that standby warms-up the task 
anyway.</li>
+     *     <li>If a target member's <em>sibling</em> hold a standby, we cannot 
borrow but, but need to move the
+     *     standby to its new owner, and putting a warmup on the target 
member, spending a warm-up slot.
+     *     (Cf case (2) of {@link #isReady(CurrentAssignmentIndex, TaskId, 
String, String)} </li>
+     * </ul>
+     *
+     * <p>Everything else <b>parks</b> -- the task keeps running on its 
current owner with nothing warming up, and a
+     * later refinement step picks it up once a warm-up slot frees.
+     *
+     * @param decisions
+     *        What the case analysis decided, from {@link #analyzeTasks}.
+     * @param members
+     *        All members of the group, used to resolve which process a task's 
current owner runs in.
+     * @param processLoad
+     *        The load of each process, from {@link #indexProcessLoad}.
+     * @param numWarmupReplicas
+     *        How many copies beyond the target assignment may exist at once, 
group-wide.
+     *
+     * @return Which warm-up tasks the intermediate assignment places, and how 
each staged migration is warmed.
+     */
+    static WarmupPlan planWarmups(
+        final TaskDecisions decisions,
+        final Map<String, StreamsGroupMember> members,
+        final Map<String, ProcessLoad> processLoad,
+        final int numWarmupReplicas
+    ) {
+        if (numWarmupReplicas == 0) {
+            return WarmupPlan.EMPTY;
+        }
+
+        final SortedMap<TaskId, String> warmupTasks = new TreeMap<>();
+        final SortedSet<TaskId> borrowedMigrations = new TreeSet<>();
+        final SortedSet<TaskId> parkedMigrations = new TreeSet<>();
+
+        final List<FundingCandidate> keptWarmups = new ArrayList<>();
+        final List<FundingCandidate> newWarmupCandidates = new ArrayList<>();
+
+        for (final StagedMigration migration : decisions.stagedMigrations()) {
+            final Warming warming = warmingOf(migration);
+            switch (warming) {
+                case PARK -> parkedMigrations.add(migration.task());
+                case BORROW -> borrowedMigrations.add(migration.task());
+                case KEEP -> keptWarmups.add(fundingCandidate(migration, 
members, warming));
+                // Both put a warm-up task on the target owner and both cost a 
warm-up slot, so they share one
+                // candidate list -- but a plant is funded ahead of a sibling 
move (see comparePriority).
+                case PLANT, SIBLING_MOVE ->
+                    newWarmupCandidates.add(fundingCandidate(migration, 
members, warming));
+            }
+        }
+
+        // Warm-up tasks already restoring are funded first. If {@code 
num.warmup.replicas} config was reduced, we might
+        // be over warmup budget and have to give up some warmup tasks. 
Evicting in reverse funding order
+        // keeps which ones deterministic rather than dependent on iteration 
order.
+        // Note: revocation of warmup task happens implicitly by not adding 
them to the assignment patch again
+        keptWarmups.sort((left, right) -> comparePriority(left, right, 
processLoad, Map.of()));
+        for (int i = 0; i < keptWarmups.size(); i++) {
+            final FundingCandidate keptWarmup = keptWarmups.get(i);
+            if (i < numWarmupReplicas) {
+                warmupTasks.put(keptWarmup.task(), keptWarmup.targetOwner());
+            } else {
+                parkedMigrations.add(keptWarmup.task());
+            }
+        }
+
+        // New warm-up tasks raises its target process's load, so we need to 
update it while we go, and find a new
+        // `best` from scratch each time
+        // note: this nested-loop is bounded by the number of unused warm-up 
slots; so while it's O(unused * candidate)
+        // it's effectively not quadratic (we can consider `unused` a constant)
+        final Map<String, Integer> newWarmupsByProcess = new HashMap<>();
+        int used = Math.min(keptWarmups.size(), numWarmupReplicas);
+
+        while (used < numWarmupReplicas && !newWarmupCandidates.isEmpty()) {
+            int best = 0;
+            for (int candidate = 1; candidate < newWarmupCandidates.size(); 
candidate++) {
+                final int comparison = comparePriority(
+                    newWarmupCandidates.get(candidate),
+                    newWarmupCandidates.get(best),
+                    processLoad,
+                    newWarmupsByProcess
+                );
+                if (comparison < 0) {
+                    best = candidate;
+                }
+            }
+
+            final FundingCandidate funded = newWarmupCandidates.remove(best);
+            warmupTasks.put(funded.task(), funded.targetOwner());
+            newWarmupsByProcess.merge(funded.targetProcessId(), 1, 
Integer::sum);

Review Comment:
   Seems like a valid concern.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to