fix(zero): size-aware predicate move timeout and rebalancer backoff - #9792
Merged
Conversation
Zero cancelled every predicate move at a hardcoded 120 minutes, so moves of large tablets (>15GB) could never complete (#9784). The constant has been bumped once before for the same reason (#6388); a fixed wall-clock value is always wrong for someone. - Scale the move timeout with tablet size: a move now gets at least size / 256KiB-per-second, using the larger of on-disk and uncompressed bytes, with the previous 2h constant as the floor. Small tablets behave exactly as before; a 20GB tablet gets ~23h instead of 2h. - Back off in the auto-rebalancer after a failed move: the cooldown doubles per consecutive failure (1h up to 24h) and is never shorter than the failed attempt itself, so Zero stops re-picking the same tablet every rebalance tick, re-streaming it for hours, and aborting commits on the predicate the whole time. Manual moves via /moveTablet bypass the backoff, and a successful move clears it. - Log the computed timeout in the move announcement so operators can see the budget a move was given. Fixes #9784 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a long-running integration test for the size-aware move timeout and rebalancer backoff, excluded from CI via the largemove build tag (no workflow compiles it). Against a real two-group cluster it: - loads MOVE_TEST_GB (default 8) GiB of incompressible values into one predicate and waits for Zero to report the tablet size, - kills the destination Alpha mid-move and asserts the move fails and Zero records the rebalancer backoff, - restarts the destination, retries, and asserts the move completes, the data survives intact, and every move announcement carried a size-scaled timeout above the 2h floor. Run with: go test -v -timeout=8h --tags=largemove ./systest/predicate-move/ See systest/predicate-move/README.md for knobs and requirements. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
shiva-istari
force-pushed
the
matthewmcneely/predicate-move-timeout
branch
from
July 28, 2026 13:14
8b9f01b to
ca799f7
Compare
shiva-istari
approved these changes
Jul 28, 2026
xqqp
reviewed
Jul 28, 2026
The induced-failure leg killed the destination Alpha at a fixed 75s, which assumed the move would still be streaming. On fast hosts it is not: an M4 laptop moves a 3.7GiB tablet in ~64s, so the test failed with 'move finished before the kill point'. Measure the host's ingest rate during the initial load and top up the data until the estimated move duration (bytes / ingest rate) is at least twice the kill point. A move cannot outrun ingest on the same host since it re-reads, rolls up, streams, and re-applies every byte through the destination's Raft, so the kill now reliably lands mid-stream. MOVE_TEST_GB becomes a floor rather than the final size. Verified on the same host: the size-scaled timeout announcement (timeout: 4h13m0s for a 3.7GiB tablet) and the full move path worked; only the kill timing needed calibration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
An Alpha killed mid-move replays several GiB of badger/WAL state on restart and needs time to re-establish cluster connections; asserting HealthCheck once immediately after StartAlpha fails with 'No connection exists'. Poll with a 10m deadline instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…urns The cluster HTTP client binds to one alpha, and the group->alpha mapping is not deterministic, so the killed destination can be the client's alpha; querying /state during the outage then fails with connection refused. Assert the tablet stayed on the source group after health returns instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Zero cancels every predicate move at a hardcoded 120 minutes (
predicateMoveTimeoutin dgraph/cmd/zero/tablet.go), so moves of large tablets can never complete. The report in #9784 is a 20GB predicate that needed about 16 hours. This constant has been raised once before for the same reason, from 20 minutes to 2 hours in #6388, so any fixed wall-clock value is eventually wrong for someone's data size.The failure mode is worse than a cancelled move. While a move runs, Zero aborts every commit on the predicate (
blockTablet, enforced in oracle.go). On timeout, all streamed work is discarded, and the auto-rebalancer deterministically re-picks the same largest tablet every--rebalance_interval(default 8m). A cluster with one oversized predicate ends up in a permanent loop: two hours of write aborts and streaming, cancel, discard, repeat.Changes
moveTimeout()grants at least size / 256 KiB-per-second, using the larger of on-disk and uncompressed bytes, with the previous 2h constant as the floor. Tablets under ~1.8GiB behave exactly as before; the 20GB tablet from Unblock large (>15GB) predicate moves by removing hardcoded 2-hour timeout #9784 gets ~23h.chooseTabletskips tablets on cooldown, a successful move clears the state, and manual moves via/moveTabletbypass the backoff.A configurable flag was considered and dropped. A floor-style flag reads ambiguously against a "timeout" name, and a cap-style flag recreates the original bug; the size-derived value needs no configuration.
Why 256 KiB/s
The receive path applies the whole stream through serial 32MB Raft proposals (
batchAndProposeKeyValuesin worker/predicate_move.go): each chunk is replicated to quorum, written to the Raft WAL, and fully applied before the next is proposed. Sustained rates in the field are therefore low; the #9784 report works out to ~0.35 MB/s on-disk. The floor rate is deliberately conservative. The throughput problem itself is out of scope here and tracked in #9790 (pipeline the receive path) and #9791 (two-phase move to shrink the commit-abort window).Testing
TestMoveTimeout,TestMoveCooldown,TestMoveBackoff).TestLargePredicateMovein systest/predicate-move runs the full scenario against a real two-group cluster. It loads several GiB of incompressible data (self-calibrating the size to the host's ingest rate so the move reliably outlasts the kill point), kills the destination Alpha mid-stream, asserts the failed move triggers the rebalancer backoff and leaves the source group intact, then restarts the Alpha, retries, and asserts the move completes with both announcements carrying the size-scaled timeout. It sits behind thelargemovebuild tag, which no CI workflow compiles; run it manually per the package README (a run loads GiBs of data and takes 15+ minutes).timeout: 9h11m59sfor the 8.68GB tablet on both attempts, the mid-stream kill produced the expected failure plus the "Skipping automatic rebalancing" backoff line, and the retry completed with all 131,296 triples intact on the destination. Observed move throughput was ~19 MiB/s against 42-68 MiB/s raw ingest even with single-replica groups, which is the receive-path bottleneck tracked in Predicate moves: pipeline destination Raft proposals and batch KV writes #9790.go build, gofmt, and trunk pass.systest/group-deleteTestNodes,systest/integration2TestUniqueMultipleGroups) moves small tablets, so the 2h floor applies and behavior there is unchanged.Fixes #9784
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.