Conversation
…replica test The rank and zero-offset delays in clusterHandleReplicaFailover only lower the probability that an empty replica wins an election, they do not forbid it. With cluster-node-timeout 1000 the ordering margin between node 4 (the only replica with data) and the empty sub-replicas is about 600 ms, so a stall on node 4 inverts the outcome and node 3 or node 7 wins instead. Set cluster-replica-no-failover on nodes 3 and 7 so that node 4 winning is an invariant rather than a timing margin. Reproduced locally 5/5 with a 2 s DEBUG SLEEP on node 4 across the election window; 0/5 after this change with the same injection. Signed-off-by: Madelyn Olson <matolson@amazon.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.
Sub-replica reports zero repl offset and rank, and fails to win electionrequires that node 4, the only replica holding data, wins the election after primary 0 dies, but the server never promised that. The rank and zero-offset delays inclusterHandleReplicaFailoveronly make an empty replica less likely to win, and with the test'scluster-node-timeout 1000the whole ordering margin is about 600 ms of wall clock on node 4. Stall node 4 for 2 s across the election window and the empty node 3 or node 7 wins instead, 5 times out of 5, which is what the sanitizer runner produced in CI. This setscluster-replica-no-failoveron nodes 3 and 7 so node 4 winning becomes an invariant instead of a timing margin.Fixes the
shutdownvariant tracked upstream as valkey-io/valkey#4672 and, because the change is in the sharedtest_sub_replicaproc, thesigstopvariant tracked as valkey-io/valkey#4265. Filed here as #43.Details
Problem
tests/unit/cluster/replica-migration.tcl:294-303asserts that after primary 0 is killed, node 4 is the new primary and nodes 3 and 7 are its replicas. Node 4 is the only replica of primary 0 with a non-zero replication offset, so it is the only node that takes the immediate-election path atsrc/cluster_legacy.c:5992-5997, becausemyselfIsBestRankedReplica()atsrc/cluster_legacy.c:165requiresgetNodeReplicationOffset(myself) != 0atsrc/cluster_legacy.c:167.Nodes 3 and 7 are penalized but not excluded.
src/cluster_legacy.c:5851:With
cluster-node-timeout 1000(tests/unit/cluster/replica-migration.tcl:356),delay = 33, and an empty rank-1 replica schedules its election at:33 + rand(33) + 66 + 500 = 600 to 630 ms. That is the entire budget. The source itself describes this as probability shaping, not exclusion:
In the CI run (https://github.com/valkey-io/valkey/actions/runs/34727724387/job/103644755446), node 4 started its election 541 ms ahead of node 3 but then logged nothing between
00:26:00.640and00:26:01.334, six missed 100 ms cron ticks, so itsFAILOVER_AUTH_REQUESTwas not flushed until node 3 had already collected both votes.Reproduction
No sanitizer and no CI machine needed. Stall node 4's event loop across the election window with
DEBUG SLEEPfrom a deferring client, inserted immediately before theif {$type == "shutdown"}block:2000 ms is the knob.
REPRO43_STALL_MS=1000is 0/5: the stall has to outlive FAIL detection, which cannot happen beforecluster_node_timeout= 1000 ms has elapsed, and then node 3's 600 ms delay on top of that.Before, 5/5 fail:
Loop 4 is new information relative to the CI report: node 7, the empty sub-replica, won. Any zero-offset replica of the dead primary can beat node 4, not just node 3.
After, same 2000 ms injection still in place, 5/5 pass:
The injection is not part of this PR.
Fix
clusterHandleReplicaFailoverreturns immediately when the config is set, atsrc/cluster_legacy.c:5864, so node 4 is the only node that can ever win the epoch. The twoif {[count_log_message -3 "Start of election"] != 0}guards attests/unit/cluster/replica-migration.tcl:308-314were already written to tolerate no election happening, so they remain valid; they simply no longer fire.Decisions a reviewer has to make
Is this a product bug? Proposal: no. Promoting an empty replica loses writes and the default configuration permits it. Election safety held in every reproduction: exactly one node won epoch 10, the rest were denied by the one-vote-per-epoch rule at
src/cluster_legacy.c:5841-5842. The existence ofCLUSTER_REPLICA_NO_FAILOVER_IF_EMPTYonunstable(src/cluster_legacy.c:6266, added by05a9c29c6, PR #4425) is the acknowledgement that the default path allows exactly this.yesorif-empty? Proposal:yeson this base,if-emptyonce rebased. This branch is offagents/unstable(596da45a0), which is 66 commits behindupstream/unstableand does not contain05a9c29c6, so the config there is still a plain bool atsrc/config.c:3377. Onupstream/unstableit is an enum atsrc/config.c:3539andif-emptyis the more precise value, since it blocks only the zero-offset case. Happy to switch if this is retargeted.Why not raise
cluster-node-timeoutinstead?delayatsrc/cluster_legacy.c:5851scales linearly with the timeout, so a proportionally larger stall reinstates the same inversion. It keeps the assertion timing-based and lengthens a test thatfa62dce94(PR #4006) deliberately shortened.Why not loosen the assertion to "some replica won"? The rest of the test would still fail:
R 3 get key_977613attests/unit/cluster/replica-migration.tcl:338-342needs the 10 KiB value that only node 4 holds, and it is genuinely lost when an empty replica is promoted.Out of scope
test_migrated_replicaattests/unit/cluster/replica-migration.tcl:22andtest_nonempty_replicaat line 140 carry the same[s -4 role] eq {master}assertion under the samecluster-node-timeout 1000. valkey-io/valkey#3992 is the open upstream issue for the first. The same two lines would fix them; not touched here.Testing
The before run above is the proof.
./runtest --single unit/cluster/replica-migrationwith no injection is clean.This was generated by AI but verified, with love, by a human.