Budget-aware low disk mode table rebalance - #19386
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #19386 +/- ##
============================================
+ Coverage 67.20% 67.59% +0.38%
- Complexity 1418 1430 +12
============================================
Files 3479 3486 +7
Lines 223075 224327 +1252
Branches 35135 35420 +285
============================================
+ Hits 149920 151630 +1710
+ Misses 61184 60663 -521
- Partials 11971 12034 +63
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Jackie-Jiang
left a comment
There was a problem hiding this comment.
Non-blocking review. The overall direction looks useful, but I found two safety issues that should be addressed or explicitly documented before relying on the new low-disk guarantee.
Critical
-
Live rebalance discards the disk ceiling when progress stalls (
TableRebalancer.java:1718)The fallback recomputes the step with
stepBudget=null, deliberately allowing servers to exceed the initial/target byte ceiling. Because pre-checks are optional and do not gate execution, a full server can still exhaust disk.Please abort with the blocked servers and required bytes, or require a separate explicit, default-off override for unsafe continuation.
-
Incomplete replay can be reported as safe (
TableRebalancer.java:2452)Exceptions and the 10,000-step limit return the same partial/empty map as a completed safe replay. The pre-check interprets an empty map as
PASS, although the actual rebalance may later take the unsafe fallback.Please return explicit
SAFE,UNSAFE, orINCOMPLETE/FAILEDstatus and add a regression requiring more than 10,000 steps.
Major
-
Synchronous replay may be prohibitively expensive (
TableRebalancer.java:2441)Up to 10,000 iterations repeatedly scan assignments, create assignment-sized maps/sets, and duplicate budget/group calculations. Please use incremental accounting or a bounded work/time budget that returns inconclusive, and benchmark production-scale tables with batching and strict routing.
-
Pre-check integration is not exercised end-to-end (
DefaultRebalancePreCheckerTest.java:183)The new failure test overrides
getServersForcedOverDiskBudget()with a hard-coded result. A wiring error acrossPreCheckContext, routing mode, batching, segment sizes, and replay would leave both test suites green. Please add a real known-over-budget fixture throughDefaultRebalancePreChecker. -
The performance claim lacks relevant measurements
The description says the bound “costs essentially nothing,” but reports mean rebalance step count rather than pre-check latency, CPU, allocation, or GC. Please add production-scale measurements before retaining that claim.
Minor
- Consider extracting the disk-budget and replay machinery into a package-private planner;
TableRebalancergrows to roughly 2,700 lines. retainInstancesThatFit()documentation says non-fitting instances are retained at the back, but the implementation filters them out.
Verification: reviewed head c4990363; focused controller tests passed (24 tests, 0 failures), and GitHub CI was green. I found no compatibility, locking, or version-checked IdealState-write regressions.
Description
Low disk mode today in table rebalance only guarantees that within a segment, the replica to offload will be done first, then the replica to add.
The scope is limited to a segment, not server. However, when servers have limited disk space, we want to further guarantee that at any step, a server won't load more bytes than they initially did if a server is to net lose some bytes, or a server won't load more bytes than it would eventually load if it's to net gain some bytes.
Current implementation won't give this guarantee (see example), and we need this guarantee to avoid jamming disk spaces. Also when the disk is already jammed such that no segments can be added to it, we need this guarantee to rebalance our way out without segments getting into error state due to no disk space.
Example of the current implementation
New design and new constraint
Scope: we only change how we derive the next step, i.e. the result returned by
getNextAssignment. Nothing change in deriving target assignment, only the intermediate steps. Also, forlowDiskMode=falsethe algorithm is the same.The illustration here are shown the segment count, but this PR generalize it to segment bytes.
DiskUsageBudget
We compute the budget of each server on how many bytes of segments they can load at most at the beginning of the rebalance.
The idea is that each server won't load more bytes than they initially did if a server is to net lose some bytes, or won't load more bytes than it would eventually load if it's to net gain some bytes.
StepDiskBudget
Compute for each step. This tells you how many bytes you can still take based on the current assignment, respect to the DiskUsageBudget we initially fixed on.
computeNextAssignment
It will only change the segment's assignment if it fits the
StepDiskBudget. Reject the new assignment if it violates, so the segment stay at its places for this step. In the case of strict replica group, the cost is computed on the entire replica group since they'll be moved together.For this mechanism, most of the cases should be able to resolve a sequence that fits the
DiskUsageBudgetconstraint entirely. The above example would become this:Precheck
No guarantee that such sequence is resolvable under the any algorithm, though. If next assignment couldn't be obtained under the constraint, it will fall back to the original low disk mode implementation.
Therefore we have a pre-check to tell if the rebalance will violate this constraint (since the sequence resolution is deterministic, we can verify that during pre-check).
It names the server, how far over it would go, and two concrete remedies.
The case it replaces
Before this PR, that same situation returned PASS, because the check assumed lowDiskMode always removed the transient usage:
That message is still returned — but now only when the replay confirms the budget actually holds, rather than being asserted unconditionally.
The other outcomes on this path, unchanged
In case of mid-flight uploaded segments
When a new segment appear in ideal state by external sources (e.g. segment upload, consuming segment committed), there are two cases:
Every step we check if there are new segments added into the ideal state that's not seen in the beginning, account for their bytes as if they were in the ideal state when we computed the
DiskUsageBudget.Notice that we don't guarantee the disk would be in the safe shape if the segments are added mid-flight because pre-check won't see that. But we'll still move the segments so that no servers would bare additional bytes.
Testing
LowDiskModeRebalanceSimulatorTestdrives the real TableRebalancer.getNextAssignment in a loop, exactly as doRebalance does, tracking every server's bytes at each step — so it exercisesproduction code, not a re-implementation. Three properties are asserted over 21 fixed scenarios:
Scenarios cover balanced and replica-group assignment, both routing modes, batchSizePerServer, skewed segment sizes, segments uploaded mid-rebalance, and the two assignments a randomized
search found hardest. main() additionally runs ~32k randomized scenarios for comparing effectiveness by hand; those are not tests.
Constraint violations, before and after
Post-change the wider sweeps — 1,944 server-set shapes and 30,000 random group structures — also report zero. Mean step count moves 3.4 → 3.5, so the bound costs essentially nothing.