2626
2727/**
2828 * A {@link BalancerStrategy} which normalizes the cost of placing a segment on a
29- * server as calculated by {@link CostBalancerStrategy} by multiplying it by the
30- * server's disk usage ratio .
29+ * server as calculated by {@link CostBalancerStrategy} by dividing by the
30+ * server's available disk headroom .
3131 * <pre>
32- * normalizedCost = cost * usageRatio
32+ * normalizedCost = cost / max(EPSILON, 1 - usageRatio)
3333 * where usageRatio = diskUsed / totalDiskSpace
3434 * </pre>
35- * This penalizes servers that are more full, driving disk utilization to equalize
36- * across the tier. When all servers have equal disk usage, the behavior is identical
37- * to {@link CostBalancerStrategy}. When historicals have different disk capacities,
38- * this naturally accounts for both fill level and total capacity.
35+ * The denominator diverges as a server approaches full, so disk fullness has
36+ * more weight over the placement decision when servers are nearly full,
37+ * regardless of asymmetries in the locality cost. {@link #EPSILON} is a small
38+ * numerical floor on the divisor to guard against division by zero (or by
39+ * negative values during in-flight loads).
3940 * <p>
40- * To prevent oscillation when servers have similar utilization , any server that
41+ * To prevent oscillation when servers have similar headroom , any server that
4142 * is already projected to hold the segment (the source on a move, or a currently
4243 * serving node on a drop) receives a cost discount equal to
43- * {@link # DEFAULT_MOVE_COST_SAVINGS_THRESHOLD}. A move therefore fires only when
44+ * {@link DiskNormalizedCostBalancerStrategyConfig. DEFAULT_MOVE_COST_SAVINGS_THRESHOLD}. A move therefore fires only when
4445 * the destination saves at least this fraction of the source's cost. The default
4546 * is configurable via
4647 * {@code druid.coordinator.balancer.diskNormalized.moveCostSavingsThreshold}.
4748 */
4849public class DiskNormalizedCostBalancerStrategy extends CostBalancerStrategy
4950{
5051 /**
51- * Default minimum fractional cost reduction required before a segment will
52- * be moved off a server that is already projected to hold it. A value of
53- * {@code 0.05} means the destination must be at least 5% cheaper than the
54- * source for the move to happen.
52+ * Numerical floor on the headroom divisor to prevent division by zero or by
53+ * negative values when {@code usageRatio >= 1.0} (possible for over-allocated
54+ * servers or during in-flight loads).
5555 */
56- static final double DEFAULT_MOVE_COST_SAVINGS_THRESHOLD = 0.05 ;
56+ static final double EPSILON = 1e-6 ;
5757
5858 private final double sourceCostMultiplier ;
5959
6060 public DiskNormalizedCostBalancerStrategy (ListeningExecutorService exec )
6161 {
62- this (exec , DEFAULT_MOVE_COST_SAVINGS_THRESHOLD );
62+ this (exec , DiskNormalizedCostBalancerStrategyConfig . DEFAULT_MOVE_COST_SAVINGS_THRESHOLD );
6363 }
6464
6565 public DiskNormalizedCostBalancerStrategy (ListeningExecutorService exec , double moveCostSavingsThreshold )
@@ -85,17 +85,16 @@ protected double computePlacementCost(
8585 return cost ;
8686 }
8787
88- // Guard against NaN propagation in the cost comparator if a server
89- // somehow reports a non-positive maxSize. Such a server cannot hold
90- // anything and will be rejected by canLoadSegment, so returning the
91- // raw cost is safe.
88+ // A server with non-positive maxSize cannot hold anything and will be
89+ // rejected by canLoadSegment; return the raw cost to avoid NaN propagation.
9290 final long maxSize = server .getMaxSize ();
9391 if (maxSize <= 0 ) {
9492 return cost ;
9593 }
9694
97- double usageRatio = (double ) server .getSizeUsed () / maxSize ;
98- double normalizedCost = cost * usageRatio ;
95+ final double usageRatio = (double ) server .getSizeUsed () / maxSize ;
96+ final double headroom = Math .max (EPSILON , 1.0 - usageRatio );
97+ double normalizedCost = cost / headroom ;
9998
10099 if (server .isProjectedSegment (proposalSegment )) {
101100 normalizedCost *= sourceCostMultiplier ;
0 commit comments