Add balance.disk_usage and balance.mode settings for dimensionally-consistent shard rebalancing (#15520) - #21343
Conversation
PR Reviewer Guide 🔍(Review updated until commit 0256021)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 0256021 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit fd87cc9
Suggestions up to commit 72abdd0
Suggestions up to commit 175a6c6
Suggestions up to commit fa5cf3a
Suggestions up to commit a1fd01e
|
|
❌ Gradle check result for 4cd59d5: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit 3b98738 |
|
❌ Gradle check result for 3b98738: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit b7a0075 |
|
❌ Gradle check result for b7a0075: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
b7a0075 to
feece43
Compare
|
Persistent review updated to latest commit feece43 |
feece43 to
a397cf1
Compare
|
Persistent review updated to latest commit a397cf1 |
a397cf1 to
b900993
Compare
|
Persistent review updated to latest commit b900993 |
|
❌ Gradle check result for b900993: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit 32a4a34 |
|
❌ Gradle check result for 32a4a34: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
32a4a34 to
14a8e61
Compare
|
Persistent review updated to latest commit 14a8e61 |
|
❌ Gradle check result for 14a8e61: null Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
14a8e61 to
600bea2
Compare
|
Persistent review updated to latest commit 600bea2 |
600bea2 to
0d8fa66
Compare
|
Persistent review updated to latest commit 0d8fa66 |
|
❌ Gradle check result for 0d8fa66: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
❌ Gradle check result for a1fd01e: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
| // is a dimensionless ratio while weightShard / weightIndex are raw shard-count | ||
| // deviations. See DISK_USAGE_BALANCE_FACTOR_SETTING javadoc for calibration guidance. | ||
| // When the average is zero (empty cluster) the disk term contributes zero. | ||
| final float avgDiskUsage = balancer.avgDiskUsageInBytesPerNode(); |
There was a problem hiding this comment.
Can you keep the avgDiskUsageInBytesPerNode() as a long so you can do integer arithmetic with the subtraction, then only convert to floating point with the final ratio? I'm not sure the values ever get large enough that the loss of precision will be significant, but it seems more natural to keep byte values as integers (even for an average).
|
@shwetathareja FYI |
|
Persistent review updated to latest commit 5fefcba |
|
❌ Gradle check result for 5fefcba: Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
5fefcba to
a1fd01e
Compare
|
Persistent review updated to latest commit a1fd01e |
|
The Full japicmp output filtered for binary-breaking markers ( That is the only incompatibility in the whole report. Everything else is Verification:
PR #21343 itself introduces no Every in-flight PR against
|
|
@AndreKurait Thanks for the analysis. I have a PR with the potential fix: #21500 |
|
Persistent review updated to latest commit fa5cf3a |
|
❌ Gradle check result for fa5cf3a: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
fa5cf3a to
175a6c6
Compare
|
Persistent review updated to latest commit 175a6c6 |
|
❌ Gradle check result for 175a6c6: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit 72abdd0 |
…search-project#15520) Introduces a new cluster-level float setting that adds a disk-usage term to the BalancedShardsAllocator weight function, allowing operators to bias shard rebalancing by actual per-shard byte size rather than purely by shard count. Setting: cluster.routing.allocation.balance.disk_usage Default: 0.0f (disabled, no behavior change) Dynamic: yes Design notes - Weight function adds theta3 * avgDiskUsageInBytesPerNode where theta3 is the new factor normalized alongside the existing shard and index weight factors. - Per-node disk usage is tracked inside ModelNode via ClusterInfo lookups at add/removeShard; the cluster-wide average is derived on demand by summing across the live ModelNode map so callers cannot forget to update it during allocateUnassigned / moveShards / tryRelocateShard. - When the factor is 0.0f the disk bookkeeping path short-circuits so the feature has zero overhead when disabled. - Non-finite values (NaN, +/-Inf) are rejected by the setting validator; the existing 0.0f floor rejects negatives. - DEBUG log lines surface disk-usage-driven relocations in moveShards and tryRelocateShard so operators can diagnose why a specific shard moved. Operator guidance The disk_usage factor multiplies raw byte counts, while the existing shard/index factors multiply counts in the 10^0 to 10^3 range. A non-zero disk_usage factor dominates unless scaled down accordingly. Recommended starting values: 1e-11 to 1e-9 relative to the shard factor default of 0.55f. Normalization is intentionally left to the operator so clusters with atypical shard-size distributions can tune per their workload. Signed-off-by: Andre Kurait <andrekurait@gmail.com>
…5520) Covers: - Weight function includes disk_usage term when factor is non-zero - avgDiskUsageInBytesPerNode stays consistent with the sum of ModelNode.diskUsageInBytes across allocateUnassigned / moveShards / tryRelocateShard (regression guard against the pre-fix drift where a stale cluster-wide total diverged from per-node state) - Setting validator rejects NaN and +/-Infinity - testDiskUsageBalanceMovesShardWhenCountBalanced: 3-node / 2-index cluster with counts balanced (3+3+3) but bytes skewed heavily onto node-0; asserts at least one large shard relocates off node-0 when the factor is enabled. - testDiskUsageBalanceNoOpWhenZero: same scenario with factor=0; asserts zero relocations. This twin proves the assertion in the non-zero test is attributable to the setting and fails loudly if the factor ever becomes a silent no-op. Signed-off-by: Andre Kurait <andrekurait@gmail.com>
…-project#15520) Adds DiskUsageBalanceIT as an end-to-end smoke covering setting activation and default-off behavior. The test suite resets the setting via @after to prevent state leaking across specs on failure. The deterministic correctness assertions for the feature live in the unit tests (DiskUsageBalanceTests); this IT verifies wiring through cluster settings plumbing, allocator reroute, and dynamic update paths. Signed-off-by: Andre Kurait <andrekurait@gmail.com>
…ancing (opensearch-project#15520) Adds an opt-in `cluster.routing.allocation.balance.mode` setting (dynamic, node-scope enum: `count` (default) | `ratio`) that selects how the shard, per-index, and disk-usage terms of the allocator weight function are expressed. count (default) Preserves historical behavior byte-identically. Shard and per-index terms are raw shard-count deltas from the cluster average; the disk-usage term is a ratio. THRESHOLD_SETTING keeps its shard-count-delta semantics. ratio All three terms are relative deviations from the per-axis cluster average: weight_shard(n) = (n.numShards - avg) / max(1, avg) weight_index(n,i)= (n.numShards(i) - avg(i)) / max(1, avg(i)) weight_disk(n) = (n.bytes - avgBytes) / max(1, avgBytes) The three balance factors (SHARD_, INDEX_, DISK_USAGE_) then operate on the same dimensionless scale and can be tuned directly relative to each other. THRESHOLD_SETTING is interpreted as a relative-deviation fraction (e.g. 0.1 = "at least 10% imbalance"); operators enabling ratio mode will typically lower threshold from its default of 1.0. Ratio mode also amplifies the weight of small indices (one misplaced shard of a 3-shard index is a 33% deviation vs 3% for a 30-shard index); this is semantically correct but changes prioritization relative to count mode. Precision fix for the disk-usage term The disk-usage weight previously computed (node.bytes - avgBytes) / avgBytes with avgBytes as float. float has ~7 significant digits, so on clusters with >~10 TB of total data the subtraction lost sub-MB deltas entirely before the divide. ShardsBalancer.avgDiskUsageInBytesPerNode() now returns double; the subtraction is done in double precision (exact for long byte counts up to 2^53 ~= 9 PB) and the divide is in double, with a single cast to float for the final weight contribution. This change applies to both count and ratio modes. Tests - DiskUsageBalanceTests: 3 unit tests covering ratio-mode normalization, zero-denominator guards, and count-mode default-preservation. - DiskUsageBalanceIT: 2 new integration tests - testRatioModeRebalancesByBytes: byte spread does not grow under ratio mode - testBalanceModeDynamicToggle: count<->ratio toggle at runtime stays green; invalid enum value is rejected with a clear error message. Signed-off-by: Andre Kurait <andrekurait@gmail.com>
72abdd0 to
fd87cc9
Compare
|
Persistent review updated to latest commit fd87cc9 |
|
Persistent review updated to latest commit 0256021 |
|
❌ Gradle check result for 0256021: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Hello @AndreKurait thank you for this PR. This feature is really needed! Do you think it is feasible to merge it and have it in 3.8 release? |
|
Hello @AndreKurait , yes thank you very much for this PR. I follow it since April as we have numerous issues with storage balancing which causes unassigned shard replicas (currently the storage balancing is by index number which is not really pertinent). Your pull request would be of great use for us, thank you for your work ! |
Summary
Resolves #15520. Adds two opt-in, default-preserving knobs to the shard allocator's weight function, plus a precision fix to the disk-usage term.
cluster.routing.allocation.balance.disk_usage0.0fcluster.routing.allocation.balance.modecount|ratiocountBoth are dynamic, node-scope. Both defaults reproduce pre-PR behavior bit-for-bit (with one exception: the disk term now subtracts in
doubleprecision instead offloat— a bug fix that only matters at very large cluster sizes; see "Precision fix" below).Background
BalancedShardsAllocator.WeightFunctionscores each (node, index) pair so the allocator can pick moves that reduce imbalance. Before this PR it looked like:Problem 1 — no disk signal
Two nodes with identical shard counts can carry wildly different byte loads when shard sizes are heterogeneous. The allocator had no signal to correct this.
Problem 2 — the three terms are not on the same scale
The existing shard and per-index terms are raw shard-count deltas. A node with 5 shards above average contributes
5.0. A naively-added disk term expressed as a ratio ((bytes - avgBytes) / avgBytes) has magnitudeO(1). Settingdisk_usage ≈ sharddoes not mean "equal influence" — the disk contribution is swamped by two orders of magnitude on any realistic cluster.What this PR does
1.
balance.disk_usage— new disk-byte balance factorAdds a third term to the weight function:
When
avgDiskUsage == 0(empty cluster) the disk term contributes zero. When the factor is0.0f(default), the term short-circuits and the allocator does not bother tracking per-shard bytes at all (seetrackDiskUsageonLocalShardsBalancer). Upgrades are zero-overhead.2.
balance.mode—count(default) vsratioResolves the units mismatch from Problem 2 opt-in, without changing any existing cluster's behavior.
countmode (default):Identical to pre-PR semantics.
threshold(default1.0) keeps its shard-count-delta interpretation ("at least one shard of imbalance").ratiomode:All three terms are relative deviations from the per-axis cluster average. The three balance factors operate on the same dimensionless scale and can be tuned directly relative to each other.
In ratio mode,
thresholdis interpreted as a relative-deviation fraction (e.g.0.1= "at least 10% imbalance"). Operators enabling ratio mode will typically also lowerthresholdfrom its default of1.0. This is documented onBALANCE_MODE_SETTING.Caveat — small-index amplification. Ratio mode amplifies the weight of indices with few shards: a single misplaced shard of a 3-shard index is a 33% deviation, while the same misplacement on a 30-shard index is a 3% deviation. This is semantically correct (small indices genuinely are more imbalanced per misplaced shard) but changes prioritization relative to count mode. Documented on the setting.
3. Precision fix for the disk term
ShardsBalancer.avgDiskUsageInBytesPerNode()previously returnedfloat.floathas ~7 significant digits, so on clusters with total disk usage above ~10 TB the(long bytes - float avgBytes)subtraction lost sub-MB deltas entirely before the divide — the disk term silently stopped seeing imbalances below roughly MB-scale. The return type is nowdouble; the subtraction is done indouble(exact forlongbyte counts up to 2^53 ≈ 9 PB) and the divide is also indouble, with a single cast tofloatfor the final weight contribution. This applies to both modes; it is a bug fix, not a behavior change at any realistic cluster size.No
@PublicApiis affected —ShardsBalanceris@opensearch.internal.Key changes
BalancedShardsAllocator— newDISK_USAGE_BALANCE_FACTOR_SETTING(dynamic, node-scope, finite-float validator rejecting NaN/±Inf) and newBALANCE_MODE_SETTING(enum).WeightFunction.weight(...)branches onratioModeand normalizes shard/index terms in ratio mode. Javadoc on both settings and onWeightFunctiondescribes the modes, the threshold interaction, and the small-index amplification caveat.BalanceMode(new) — small internal enum with aparse(String)that surfaces a clear error for unknown values.ModelNode/LocalShardsBalancer/ShardsBalancer— tracksdiskUsageInBytesonly when the disk factor is non-zero (seetrackDiskUsage);avgDiskUsageInBytesPerNodeisdoubleand recomputed from live model state (not a stale snapshot).ClusterSettings— one-line registrations alongside existingbalance.*settings.Testing
Unit tests (
DiskUsageBalanceTests):avgDiskUsageInBytesPerNodestays consistent with per-node state acrossallocateUnassigned/moveShards/tryRelocateShard(regression guard),testDiskUsageBalanceMovesShardWhenCountBalancedsets up a 3-node / 2-index cluster with counts balanced (3+3+3) but bytes skewed onto node-0 and asserts ≥1 large shard relocates once the factor is enabled; its twintestDiskUsageBalanceNoOpWhenZeroruns the same scenario at factor=0.0fand asserts zero relocations,testRatioModeNormalizesAllThreeTermsasserts the expected algebra for all three terms,testRatioModeHandlesZeroDenominatorsguards themax(1, avg)divisor paths,testDefaultModeKeepsRawCountDeltaspins count-mode semantics.Integration tests (
DiskUsageBalanceIT, 4 specs, all pass locally in 14.4s):testDiskUsageBalanceRebalancesByBytes— end-to-end count-mode disk rebalance: byte spread does not grow when disk_usage balance is enabled.testDefaultBehaviorUnchanged— default0.0fis byte-spread-agnostic (allocator strictly count-balances).testRatioModeRebalancesByBytes(new) — withbalance.mode=ratio,disk_usage=1.0f,threshold=0.1f: cluster stays green, all shards assigned, byte spread does not exceed count-mode baseline.testBalanceModeDynamicToggle(new) — togglesbalance.modebetweenratioandcountfour times at runtime; ensures theaddSettingsUpdateConsumerpath rebuilds the weight function without leaving shards unassigned. Also asserts thatbalance.mode=bogusis rejected with a clear error.Suite resets all three settings via
@Afterto prevent state leaking across specs on failure.Migration
None.
balance.disk_usagedefaults to0.0f(disabled).balance.modedefaults tocount(pre-PR behavior).All settings are dynamic — enable/tune/disable at runtime without restart.
Unrelated included fix
Commit on this branch removes too-short
assertBusytimeouts inIndicesRequestCacheCleanupIT— a known flaky tracked under #21397. The fix is also up standalone as #21494 so it can land onmainindependently. If #21494 merges first, that commit falls out of this branch on rebase.