db-17 step 01 — Leader election
Goal
A cluster of nodes followers, started cold, must elect exactly one
leader in a bounded number of ticks, and that leader must remain
stable as long as it can deliver heartbeats. The election protocol
must be byte-deterministic across Rust, Go, and C++.
Tasks
-
Persistent state. Each
RaftNodecarriescurrent_term: u64,voted_for: Option<u32>, andlog: Vec<LogEntry>. The dump encodesvoted_for=Noneas the signed integer-1(i64 LE);Some(id)becomesid as i64. -
Election timer.
reset_election_timer(t)setselection_deadline = t + 150 + splitmix64(seed ^ node_id ^ t) % 150. Heartbeat-due ist + 50. -
on_tick(t). Followers and candidates that hitelection_deadlinestart a new election: bumpcurrent_term, vote for self, broadcastRequestVoteto all peers, transition toCandidate. Leaders that hitheartbeat_duebroadcast an emptyAppendEntries(heartbeat). -
RequestVotehandling. Grant a vote iff (a)term == current_term, (b)voted_forisNoneor equal to the candidate, and (c) the candidate's log is at least as up-to-date as ours (the standardlast_log_term/last_log_indexlex compare). Grant resets the election timer. -
RequestVoteReplyhandling. A candidate that collects a majority ofgrantedreplies in the same term transitions toLeader, initializesnext_index[p] = log.len()andmatch_index[p] = 0for every peerp, and immediately broadcastsAppendEntries(initial heartbeat). -
become_follower(term). Used whenever a node seesterm > current_term(in any RPC). Setscurrent_term = term, clearsvoted_for, resets the election timer, transitions toFollower.
Acceptance
Inline unit tests in each language:
splitmix64_known_vectors—splitmix64(0) == 0xE220A8397B1DCDAF(the value Vigna's reference C produces).election_timer_in_range— 1000 consecutive resets all land in[t+150, t+300).request_vote_grants_first_only— vote for candidate A, then a RequestVote from B in the same term is denied.become_leader_from_majority— 3-node cluster, two RequestVoteReply withgranted=truetransitions the candidate to Leader.term_bump_demotes_leader— a Leader receiving any RPC withterm > current_termbecomes Follower and clearsvoted_for.
All five green in Rust, Go, and C++.
Discussion prompts
- Why is
voted_forpersistent (in the canonical dump) butcommit_indexvolatile (also dumped, but only because the dump is a debug oracle, not a recovery file)? - What goes wrong if you reset the election timer on send of RequestVote instead of on grant of someone else's vote? (Hint: split-vote loops.)
- Why must "majority" be computed against
nodes, not againstnodes that have replied?