Build Raft — consensus you can defend (12 scenes)
Scene 10 · Operational reality — pipelining, batching, and the no-op trick
The no-op-on-election rule does triple duty: Figure 8 fix, ReadIndex correctness, single-server change safety. Plus the throughput knobs (pipelining, batching) and graceful TimeoutNow handoff that separate paper Raft from deployed Raft.
Previously
Across earlier scenes the phrase 'no-op on election' has been mentioned three times — scene 5 used it to commit prior-term entries via the current-term rule (Figure 8), scene 7 used it to close the 2015 single-server membership-change hole, and scene 9 used it to make ReadIndex correct from the moment of election. Each time, the rule was a forward reference. This scene looks at it directly and reveals that the same one log entry is doing all three jobs.
Scene 10
Operational reality — the no-op trick, pipelining, batching, fsync
Diagram
Three Raft servers {S1, S2, S3} laid out as a row of cells. Each cell carries a role badge, the server's currentTerm, a log strip of recent entries, and commitIndex / lastApplied markers. Log entries are tinted by the term that wrote them; no-op entries render with a distinct ◦ glyph. AppendEntries arrows are blue (heartbeats faded), RequestVote arrows are amber, and the caption strip under the diagram tells you which operational dimension the slider is exercising right now.
Sources
- paperConsensus: Bridging Theory and Practice — Chapter 9 (Extensions)
- paperIn Search of an Understandable Consensus Algorithm — §6 + §8
- codeetcd-io/raft — design.md (pipelining, batching, ReadIndex)
- blogImplementing Raft in Rust (TiKV) — Operational Notes
- blogWhy etcd elections take 1 second (and what that means)
- codehashicorp/raft — leadership transfer + TimeoutNow
no-op @ idx=4, term=4
You've seen the same rule mentioned three times already — in scene 5 (the Figure 8 fix), in scene 7 (the single-server membership-change fix), and in scene 9 (ReadIndex correctness). Each time it was a forward pointer to here. It's time to look at it directly.
The rule, in plain English: the moment a server becomes leader, it appends one empty log entry of its current term and replicates it. That single act discharges three correctness obligations the curriculum has been collecting. The formal name: **no-op on election**. Formal statement: a newly-elected leader appends and replicates a single empty (no-op) entry of its own term before serving any other work.
S2 has just won the election for term 4. Watch the first AppendEntries it broadcasts — it carries exactly one entry, the no-op. The next phase reveals why the same entry shows up in three otherwise-disjoint correctness proofs.
Implementation
Leader.onElectionWin
one routine, three downstream sites — Figure 8, ReadIndex, single-server change
1def onElectionWin(self, newTerm):2 # 1. append a no-op entry of OUR term — the load-bearing trick3 noop = LogEntry(term=newTerm, payload=NOOP, index=self.lastIndex+1)4 self.appendAndReplicate(noop)5 # 2. block downstream operations until the no-op commits6 self.pendingReadIndexMessages = [] # scene 97 self.pendingConfigChanges = [] # scene 7 (Ongaro 2015 fix)8 self.waitForCommit(noop.index)9 # 3. once committed, three obligations discharged at once:10 # a. prior-term entries on this majority commit transitively11 # (current-term commit rule, scene 5 / Figure 8)12 # b. commitIndex now reflects OUR term — ReadIndex safe13 # (scene 9, drains pendingReadIndexMessages)14 # c. configuration changes may now be appended15 # (scene 7, drains pendingConfigChanges)