Build Raft — consensus you can defend (12 scenes)
Scene 3.5 · Pre-Vote and CheckQuorum — fixing the disruptive server
A partitioned member's currentTerm runaway forces a healthy leader to step down on heal. Pre-Vote probes without bumping term; CheckQuorum self-deposes a leader that can't reach a majority. Together they close the §9.6 disruption + the partial-omission liveness hole.
Previously
The election scheme in scene 3 has a real failure mode. Picture a server that briefly loses network access — when it cannot reach the leader it assumes the leader is dead, so it starts an election and bumps its currentTerm. Reconnect it, and its higher term forces the (perfectly healthy) leader to step down (per the universal step-down rule from scene 2). The cluster re-elects for no reason. We call this the **disruptive-server bug**, and this scene installs the two production fixes for it.
Scene 3.5
Pre-Vote and CheckQuorum — fixing the disruptive server
Diagram
Five servers (S1..S5) in a row. Each cell shows the server's role badge (follower / candidate / leader), its currentTerm, and a horizontal log strip. A vertical dashed wall during Show isolates S5 from the rest — that is the network partition from scene 1. Arrows between cells are RPCs (the network messages we named in scene 3): blue solid = AppendEntries heartbeat, amber = RequestVote, an ✕ on an arrow = the receiver rejected it. Small chips beside the cells light when a leader is forced to step down or when a trial vote is rejected — these are the events the scene is about.
S1 is the leader at term 1, sending heartbeats to S2..S4. S5 is on the wrong side of a network partition — it cannot reach anyone else. Every time S5's election timeout fires (recall from scene 3: the randomized 150–300 ms window after which a follower that has heard nothing from a leader becomes a candidate) it does exactly what scene 3 said to do: it bumps its own currentTerm and tries to start an election. Nobody can answer. Watch its term run away.
Implementation
Candidate.preVoteProbe / Server.onPreVote
probe peers WITHOUT bumping currentTerm; voter is read-only
1# Pre-Vote — added BEFORE the real RequestVote.2on election_timeout:3 reset election_timeout4 # PROBE: ask peers if they would grant at term+1,5 # WITHOUT bumping currentTerm.6 pre_votes = { self }7 for peer in cluster_minus_self:8 send PreVote {9 term: currentTerm + 1, # hypothetical10 candidateId: self,11 lastLogIndex: log.lastIndex,12 lastLogTerm: log.lastTerm,13 } -> peer14 # Only if a majority would grant, bump currentTerm15 # and fire the real RequestVote.1617on receive PreVote(req):18 # No state mutation — this is read-only.19 would_grant = req.term > currentTerm20 AND isAtLeastAsUpToDate(req.lastLogIndex,21 req.lastLogTerm, log)22 AND not heard_from_leader_recently()23 reply { term: currentTerm, voteGranted: would_grant }