Build Raft — consensus you can defend (12 scenes)
Scene 03 · Leader election — vote, majority, restriction
Election timeout → candidate → RequestVote → majority grants → leader. Voters grant only if the candidate's log is at-least-as-up-to-date — a placeholder predicate refined precisely in scene 6.
Previously
Scene 2 gave us a term — a monotonically-increasing integer that segments time and makes any server reset to follower the moment it sees a number bigger than its own. The obvious next question: which server is the leader of the current term? Each term needs at most one, and the cluster has to pick one without a coordinator. That picking process is leader election.
Scene 03
Leader election — vote, majority, restriction
Diagram
Five servers (S1..S5) drawn as cells in a row. Each cell shows the server's id, role badge (FOLLOWER / CANDIDATE / LEADER — same three roles from scene 2), currentTerm (the latest term it has seen), votedFor (who, if anyone, it has voted for in the current term), and a horizontal log strip whose entries are colored by the term that created them. Arrows between cells render vote-request and heartbeat messages — amber for vote requests, blue (dashed when carrying no new entries) for heartbeats; a check mark means the vote was granted, an X means refused.
election timeout fires →
Now that we have a term that monotonically segments time, the obvious question is: which server is the leader of the current term? A follower that hears nothing from a leader for a while gives up waiting and tries to become leader itself. The 'while' is a random wait — somewhere between 150 and 300 ms in the standard configuration — and we call it the **election timeout**: the randomized interval after which a follower that has received no contact from a leader transitions to **candidate** and begins an election. Watch: S1's timeout fires first. It bumps its **currentTerm** from 1 to 2, marks itself with **votedFor=S1** (the persisted record of who, if anyone, this server has voted for in the current term — at most one per term), and sends every peer a 'will you vote for me?' message. The systems term for that message is a **RequestVote RPC** — short for Remote Procedure Call, which just means 'a function call sent over the network'; the call carries the candidate's term, its id, and the position of its last log entry so the voter can decide whether to grant. Four yes-replies come back; combined with its own self-vote that is 5 out of 5, comfortably above the **majority quorum** (more than half of the cluster — for 5 servers, that's 3). S1 becomes leader of term 2 and immediately sends an empty append (a **heartbeat**) to tell every follower it is in charge.
Implementation
Candidate.beginElection
election timeout fires; candidate broadcasts RequestVote
1on election_timeout: # randomized 150–300 ms2 role = candidate3 currentTerm += 14 votedFor = self5 persist(currentTerm, votedFor)6 votes = { self }7 reset election_timeout8 for peer in cluster_minus_self:9 send RequestVote {10 term: currentTerm,11 candidateId: self,12 lastLogIndex: log.lastIndex,13 lastLogTerm: log.lastTerm,14 } -> peer
Voter.onRequestVote(req)
grant iff free vote AND candidate's log up-to-date
1on receive RequestVote(req):2 # (universal step-down already handled at handler top — see scene 2)3 up_to_date = isAtLeastAsUpToDate(4 req.lastLogIndex, req.lastLogTerm, log,5 )6 # PLACEHOLDER — precise predicate is sharpened in scene 6 (§5.4.1).7 if (votedFor == null OR votedFor == req.candidateId) AND up_to_date:8 votedFor = req.candidateId9 persist(currentTerm, votedFor)10 reply { term: currentTerm, voteGranted: true }11 else:12 reply { term: currentTerm, voteGranted: false }
Candidate.onRequestVoteReply
majority of grants → leader; broadcast heartbeat
1on receive RequestVoteReply(reply, from peer):2 if reply.term > currentTerm: # (step-down at handler top)3 return4 if reply.voteGranted:5 votes.add(peer)6 if |votes| >= majority(cluster.size): # ⌊N/2⌋ + 17 role = leader8 initialize nextIndex[p] = log.lastIndex + 1 for p in cluster9 initialize matchIndex[p] = 0 for p in cluster10 broadcast empty AppendEntries (heartbeat) # claim leadership