J-HowHuang opened a new pull request, #19386:
URL: https://github.com/apache/pinot/pull/19386
## 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
```
3 servers → 3 servers with 2 in the overlap, replication 2, 6 segments,
minAvailableReplicas=1:
segment start step 1 step 2 step 3 step 4 = target
s0 A,B B B,C B,C B,C
s1 A,C C C,D D B,D
s2 B,C C C,D C,D C,D
s3 A,B B B,C B,C B,C
s4 A,C C C,D D B,D
s5 B,C C C,D C,D C,D
segments hosted
A 4 0 0 0 0
B 4 2 2 2 4
C 4 4 → 6 ← 4 4
D 0 0 4 4 4
```
## 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, for `lowDiskMode=false` the 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 `DiskUsageBudget` constraint entirely. The above example would
become this:
```
segment start step 1 step 2 step 3 step 4 = target
s0 A,B B B B B,C
s1 A,C C C,D D B,D
s2 B,C C C,D C,D C,D
s3 A,B B B B B,C
s4 A,C C C,D D B,D
s5 B,C C C,D C,D C,D
segments hosted
A 4 0 0 0 0
B 4 2 2 2 4
C 4 4 → 4 ← 2 4
D 0 0 4 4 4
```
### 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).
```
"preChecksResult": {
"diskUtilization": {
"preCheckStatus": "ERROR",
"message": "UNSAFE. Servers with unsafe disk utilization DURING
rebalance (>=50%): Server_1 (52%). lowDiskMode cannot avoid it for this target
assignment: the rebalance cannot make
progress without going over the disk these servers start with, by up to
Server_1 (150B). Rebalance to a target assignment that frees up space on them
first, or add capacity"
}
}
```
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:
```
{
"preCheckStatus": "PASS",
"message": "Within threshold (<50%) AFTER rebalance. Servers that would
go over it DURING the rebalance: Server_1 (52%). lowDiskMode avoids that
transient disk usage by deleting segments
before adding the new ones"
}
```
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
```
PASS Within threshold (<50%)
```
```
ERROR UNSAFE. Servers with unsafe disk utilization AFTER rebalance
(>=50%): Server_1 (52%)
```
```
ERROR UNSAFE. Servers with unsafe disk utilization DURING rebalance
(>=50%): Server_1 (52%).
Enable lowDiskMode to delete segments before adding the new ones
```
### 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:
1. They are added to the ideal state as the target assignment. This case the
rebalance steps outcome won't change
2. They are added to the ideal state that's different to their target
assignments. For example when strict replica routing is in place. This case, it
might lead to a different computeNextAssignment result.
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
`LowDiskModeRebalanceSimulatorTest` drives the real
TableRebalancer.getNextAssignment in a loop, exactly as doRebalance does,
tracking every server's bytes at each step — so it exercises
production code, not a re-implementation. Three properties are asserted
over 21 fixed scenarios:
1. the rebalance always reaches the target assignment;
2. no server exceeds max(bytes it held that this rebalance did not place,
bytes the target places on it), unless the disk-utilization pre-check names it
up front;
3. under strict replica group routing, segments of a partition are never
assigned different instances.
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
```
┌─────────────────────────────────────┬────────────────────────────────────────┬───────┐
│ corpus │ before
│ after │
├─────────────────────────────────────┼────────────────────────────────────────┼───────┤
│ old/new server sets, 648 │ 120 over-allocate (18.5%), worst
2.00× │ 0 │
├─────────────────────────────────────┼────────────────────────────────────────┼───────┤
│ strict replica group, 324 │ 92 over-allocate (28.4%), worst
1.78× │ 0 │
├─────────────────────────────────────┼────────────────────────────────────────┼───────┤
│ random non-uniform assignments, 400 │ 47 over-allocate (11.8%), worst
2.00× │ 0 │
└─────────────────────────────────────┴────────────────────────────────────────┴───────┘
```
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.
--
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]