Skip to content

Deflake ccov: Contain slot migration test failures with a recovery barrier#4223

Open
rainsupreme wants to merge 4 commits into
valkey-io:unstablefrom
valkey-rainfall:deflake/migrateslots-guards
Open

Deflake ccov: Contain slot migration test failures with a recovery barrier#4223
rainsupreme wants to merge 4 commits into
valkey-io:unstablefrom
valkey-rainfall:deflake/migrateslots-guards

Conversation

@rainsupreme

@rainsupreme rainsupreme commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

When a test in cluster-migrateslots.tcl fails an assertion mid-migration, it leaks state: the in-flight migration, DEBUG SLOTMIGRATION PREVENT-* flags, config tweaks (rdb-key-save-delay, repl-timeout), and displaced slot ownership. The framework recovers from the assertion and continues, but the next code to touch the cluster hits a non-assertion error (ERR I am already migrating slot, MOVED, ERR Slots are not served by this node), which aborts the entire test client and discards all remaining tests in the run. Issue 2692 fixed one instance of this cascade at the seeding test; this PR addresses the amplification mechanism itself, which is also the dominant way this file destroys Codecov runs (coverage-side data in issue 4153). Leaked config even manufactures downstream "flakes": a failed run of the repl-timeout test leaves rdb-key-save-delay 100000 set, injecting 100 seconds of artificial snapshot delay (1000 keys x 100ms) into the restart-test loop that follows.

Commit 1 adds recover_slot_migration_state, a best-effort recovery barrier called after each migration-starting test observed to seed cascades (one call inside the parameterized restart loop covers its 8 variants): cancel in-flight migrations on all primaries, reset the leaked debug flags and configs, wait for all migration jobs to reach a terminal state, and wait for cluster convergence. Every step is catch-wrapped — the barrier runs at block level, where an escaping error would itself abort the client.

Commit 2 adds slot ownership restoration, since cancelling a migration does not move slots back: capture the canonical slot-to-primary layout once at cluster setup, and in the barrier — after migrations drain — migrate any displaced ranges back to their canonical owners.

Verification (local)

Full file passes 105/105 with the barrier, identical to baseline — the barrier is a no-op beyond cheap checks when tests pass.

Forced-failure experiments: injecting fail immediately after MIGRATESLOTS starts in the restart loop costs, with the barrier, 8 [err] with all remaining 97 tests passing; the identical injection without the barrier reproduces the historical cascade — 1 [err], then [exception]: ERR I am already migrating slot, client abort, 24 tests discarded. Forcing a failure that leaves slot ownership displaced costs exactly one [err] with the restore, and causes collateral failures in later tests without it.

Soak results (gcov-instrumented builds matching the Codecov job, 50 loops x 4 shards per mode, fork CI)

Run 1 — barrier only vs its base commit (https://github.com/valkey-rainfall/valkey/actions/runs/29466298795): baseline retained 11105 of 21000 [ok] results (53%) with 3 client aborts; barrier retained 17098 (81%) with 2 aborts, and the already migrating slot abort class dropped to zero.

Run 2 — barrier + ownership restore vs base (https://github.com/valkey-rainfall/valkey/actions/runs/29621384929): baseline retained 9062 (43%) with 3 aborts; guarded retained 18304 (87%) with 2 aborts, with two of four guarded shards fully contained — one flaky [err], zero aborts, everything else retained.

Per-iteration flake rates are unchanged between modes in both runs, as expected — this PR reduces the blast radius of a failure, it does not deflake the failing tests themselves (root-cause fixes for the top flakes are planned separately).

Known limitations

The two guarded-mode aborts in run 2 have understood causes, left as future work: (1) the ownership restore is single-shot and silent-on-failure — it can race the just-cancelled migration and give up, so a retry/verify loop would harden it; (2) the barrier covers the observed cascade-seeding tests, not every migration-starting test, so an unguarded test (e.g. "FLUSH on target during import" in run 2) can still seed a cascade — per-test coverage is possible if maintainers prefer it; (3) block-level setup code between tests executes outside any test body and remains exposed to leaked state.

References

Issue #2692 (prior instance of this cascade, fixed per-test)
issue #4153 (coverage-job impact data)
issue #3685 (an open flake in this file that this PR contains but does not fix).

When a test in cluster-migrateslots.tcl fails an assertion mid-migration,
it leaks state: the in-flight migration, DEBUG SLOTMIGRATION PREVENT-*
flags, and config tweaks (rdb-key-save-delay, repl-timeout). The next
test to call CLUSTER MIGRATESLOTS then hits a non-assertion error such
as "ERR I am already migrating slot", which the test framework re-raises
as an exception that aborts the entire test client, discarding all
remaining tests in the run. Issue 2692 root-caused one instance of this
cascade; gcov soak runs show it is the general failure amplification
mode for this file (6 of 8 soak shards ended early this way).

Add a best-effort recovery barrier (cancel in-flight migrations, reset
leaked debug/config state, wait for jobs to reach a terminal state and
for cluster convergence) and call it after the migration-starting tests
observed to seed cascades, including inside the parameterized restart
test loop. A failing test now costs one [err] instead of the rest of
the run.

Signed-off-by: Rain Valentine <rsg000@gmail.com>
The recovery barrier cancels leaked migrations and resets debug/config
state, but a test that fails after slots have moved leaves ownership
displaced from the layout later tests assume. Their commands then fail
with MOVED or "ERR Slots are not served by this node" -- non-assertion
errors that abort the whole test client (observed in 2 of 4 guard-mode
gcov soak shards).

Capture the canonical slot-to-primary layout once at cluster setup and,
in the barrier after migrations drain, migrate any displaced ranges back
to their canonical owners. Contiguous displaced slots with the same
source and destination move as one range. No-op on the happy path
beyond a CLUSTER SLOTS comparison; all steps best-effort.

Signed-off-by: Rain Valentine <rsg000@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 80e998af-7e91-4a8d-9e36-746309e0a489

📥 Commits

Reviewing files that changed from the base of the PR and between a472de7 and f72c29c.

📒 Files selected for processing (1)
  • tests/unit/cluster/cluster-migrateslots.tcl
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/cluster/cluster-migrateslots.tcl

📝 Walkthrough

Walkthrough

The migration test suite now captures the cluster’s initial slot ownership, detects and drains active migrations, restores displaced slot ranges to their canonical shard primaries, and invokes this recovery barrier across migration-related test flows.

Changes

Slot migration recovery

Layer / File(s) Summary
Canonical slot layout capture
tests/unit/cluster/cluster-migrateslots.tcl
Adds migration detection and records canonical ownership for all 16,384 slots during cluster setup.
Migration state and slot ownership repair
tests/unit/cluster/cluster-migrateslots.tcl
Clears migration controls, resets selected configurations, cancels active migrations, waits for drainage, and restores displaced slot ranges to their canonical shard primaries.
Recovery barrier integration
tests/unit/cluster/cluster-migrateslots.tcl
Runs recovery after migration-related test blocks and during restart-during-migration handling.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: murphyjacob4, sarthakaggarwal97

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a recovery barrier to contain slot migration test failures.
Description check ✅ Passed The description is detailed and directly matches the slot-migration recovery and ownership-restoration changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.80%. Comparing base (7bb7b7f) to head (f72c29c).
⚠️ Report is 13 commits behind head on unstable.

Additional details and impacted files
@@             Coverage Diff              @@
##           unstable    #4223      +/-   ##
============================================
- Coverage     76.83%   76.80%   -0.03%     
============================================
  Files           162      162              
  Lines         81433    81477      +44     
============================================
+ Hits          62565    62577      +12     
- Misses        18868    18900      +32     

see 20 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@valkey-review-bot valkey-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found one correctness issue in the new ownership-restore path. The recovery barrier can't restore displaced slots back from a promoted replica, so a failed failover-path test can still leak slot ownership into later tests.

Comment thread tests/unit/cluster/cluster-migrateslots.tcl Outdated
@rainsupreme
rainsupreme marked this pull request as ready for review July 20, 2026 22:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/cluster/cluster-migrateslots.tcl`:
- Around line 265-271: Update recover_slot_migration_state to reset each
server’s hz configuration to its normal default alongside rdb-key-save-delay and
repl-timeout, using the existing CONFIG SET pattern. This cleanup must run for
every server even when the “Import with hz set to 1” test exits early, so the
subsequent barrier sees restored cron timing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 56e1fb6a-9529-4044-8606-8d3b6dfb150f

📥 Commits

Reviewing files that changed from the base of the PR and between 6b006c9 and a472de7.

📒 Files selected for processing (1)
  • tests/unit/cluster/cluster-migrateslots.tcl

Comment thread tests/unit/cluster/cluster-migrateslots.tcl
The hz-throttled import test lowers hz to 1 on two nodes and only
restores it in its final lines, so a mid-test failure leaks the slowed
cron tick into every subsequent test. Reset hz to its default alongside
the other leaked configs.

Signed-off-by: Rain Valentine <rsg000@gmail.com>
The canonical layout only recorded the three capture-time primaries, so
slots owned by a replica promoted during a failover test were invisible
to the restore path and stayed displaced after the barrier ran. Track
ownership at shard granularity instead: record every node's ID and
shard membership at capture time, treat a slot as displaced only when
it sits on the wrong shard, and migrate it back to whichever member of
the canonical shard is currently its primary. Cancelling and draining
migrations now also covers all nodes, since after a failover the
primary holding a migration job may be a promoted replica.

Verified with a forced failure that promotes a replica and leaves a
slot displaced onto it: the following barrier migrates the slot back to
its canonical shard, where previously it stayed displaced. Full file
passes 105/105 unchanged.

Signed-off-by: Rain Valentine <rsg000@gmail.com>
@rainsupreme

Copy link
Copy Markdown
Contributor Author

This restorer idea could potentially be generalized to all tests by integrating it with the on-failure hook, in which case it would restore some per-file invariant state. The advantage is that nothing changes when tests pass, but it wouldn't work for tests that depend on state set by a prior test). This refactor would be far less targeted of a fix, so I didn't do it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant