FrankChen021 commented on code in PR #19884:
URL: https://github.com/apache/druid/pull/19884#discussion_r3720541485


##########
server/src/main/java/org/apache/druid/server/coordinator/loading/StrategicSegmentAssigner.java:
##########
@@ -615,8 +615,16 @@ private int updateReplicasInTier(
     final int movingReplicas = replicaCountOnTier.moving();
     final boolean shouldCancelMoves = requiredReplicas == 0 && movingReplicas 
> 0;
 
+    // A replica serving under a partial-load profile is pinned by a 
partial-load rule, but we got here through the
+    // regular full-load path, so that rule no longer applies: the 
datasource's partial-load rule was replaced or
+    // shadowed by a higher-priority load rule, or its matcher stopped 
resolving and fell through to FULL_LOAD. That
+    // replica needs an in-place reload carrying the plain unwrapped load spec 
so the historical releases its rule
+    // holds. When the tier wants no replicas at all we skip it, the drops 
below are already on their way and dropping
+    // clears the rule on the historical.
+    final int replicasToRevert = requiredReplicas > 0 ? 
replicaCountOnTier.loadedWithPartialProfile() : 0;

Review Comment:
   [P2] Revert profiles during partial-to-broadcast transitions
   
   This reconciliation is only reached through `replicateSegment`, while 
`BroadcastDistributionRule` calls `broadcastSegment`; its 
`loadBroadcastSegment` immediately returns for an already-serving historical. 
Replacing a partial rule with a broadcast rule therefore never sends the 
unwrapped reload, leaving the old partial profile and rule holds indefinitely. 
Apply equivalent reversion in the broadcast path and cover this transition.



##########
server/src/main/java/org/apache/druid/server/coordinator/loading/StrategicSegmentAssigner.java:
##########
@@ -645,20 +653,54 @@ private int updateReplicasInTier(
     }
 
     // Cancel loads and queue drops if the projected count exceeds the 
requirement
+    int dropsQueuedOnTier = 0;
     if (projectedReplicas > requiredReplicas) {
       int replicaSurplus = projectedReplicas - requiredReplicas;
       int canceledLoads =
           cancelOperations(SegmentAction.LOAD, replicaSurplus, segment, 
segmentStatus);
 
       int numReplicasToDrop = Math.min(replicaSurplus - canceledLoads, 
maxReplicasToDrop);
       if (numReplicasToDrop > 0) {
-        int dropsQueuedOnTier = dropReplicas(numReplicasToDrop, segment, tier, 
segmentStatus);
+        dropsQueuedOnTier = dropReplicas(numReplicasToDrop, segment, tier, 
segmentStatus);
         incrementStat(Stats.Segments.DROPPED, segment, tier, 
dropsQueuedOnTier);
-        return dropsQueuedOnTier;
       }
     }
 
-    return 0;
+    // Release partial-load rules that no longer apply. Done last so the 
load/drop decisions above claim their
+    // servers first: a replica that just picked up an action is no longer 
`isServingSegment`, so it is skipped here
+    // and reverted on a later run if it is still around.
+    if (replicasToRevert > 0) {
+      final int reverted = revertPartialProfileReplicas(segment, tier);
+      if (reverted > 0) {
+        incrementStat(Stats.Segments.PARTIAL_RULE_REVERTED, segment, tier, 
reverted);
+      }
+    }
+
+    return dropsQueuedOnTier;
+  }
+
+  /**
+   * Queues an in-place reload on every server in {@code tier} that serves 
{@code segment} under a
+   * {@link PartialLoadProfile}. The request carries the plain unwrapped 
{@code segment}, which is what tells the
+   * historical to release its rule holds rather than apply or swap one.
+   * <p>
+   * The replica count is deliberately left alone: these servers <em>are</em> 
serving, so they still satisfy the
+   * rule's replication requirement and must not be double-counted as a 
deficit. This only refreshes what they hold.
+   * <p>
+   * Servers with any queued action are skipped via {@link 
ServerHolder#isServingSegment}, which covers both the
+   * load/drop decisions made earlier in this run and operations left over 
from a previous one.
+   */
+  private int revertPartialProfileReplicas(DataSegment segment, String tier)
+  {
+    int numReverted = 0;
+    for (ServerHolder server : cluster.getManagedHistoricalsByTier(tier)) {
+      if (server.isServingSegment(segment)
+          && server.getServer().getPartialLoadProfile(segment.getId()) != null
+          && loadQueueManager.loadSegment(segment, server, SegmentAction.LOAD, 
null)) {

Review Comment:
   [P1] Honor the per-server load queue limit
   
   `ServerHolder.startOperation` does not reject operations after 
`maxSegmentsInNodeLoadingQueue` is reached; normal load paths first check 
`canLoadSegment` or `isLoadQueueFull`. This loop directly queues every profiled 
segment, so changing a large datasource to a full-load rule can enqueue all 
reverts on each historical in one coordinator run, bypassing the configured cap 
and flooding the peon/historical. Check the queue budget before enqueueing and 
add a capped-queue test.



-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to