Date: 2026-05-29
Time: 13:50
advancecommit_index — Raft Leader Commit AdvancementThis method implements the leader's commit index advancement rule from the Raft paper (§5.3/§5.4). A leader cannot simply commit an entry the moment it writes it to its own log — it must wait until a majority of the cluster has replicated that entry. This method scans uncommitted log entries, counts how many nodes have replicated each one, and advances commitindex to the highest entry replicated by a majority.
It also enforces a critical safety property: a leader only commits entries from its own term, never from a prior leader's term. Entries from prior terms get committed indirectly — once a current-term entry at a higher index is committed, all preceding entries are implicitly committed too. This prevents the scenario in Figure 8 of the Raft paper, where a leader could incorrectly commit an old-term entry that later gets overwritten.
Preconditions:
self.state == "leader"). Both call sites (tick and handleappend_response) verify this before invoking.self.matchindex reflects the latest known replication state of each peer.self._log is consistent and indexed starting from a sentinel at position 0.Postconditions:
self.commitindex is monotonically non-decreasing — it either stays the same or advances forward.self.commitindex is the highest log index n where: (a) self.log[n].term == self.current_term, and (b) a strict majority of nodes (including the leader) have replicated index n.Invariant:
None — this is a private method that reads directly from instance state:
self.commitindex: current commit watermarkself._log: the replicated log (list of LogEntry, 0-indexed with a sentinel)self.currentterm: the leader's current termself.peerids: list of peer node IDsself.matchindex: dict mapping each peer to the highest log index known to be replicated on that peerNone. This method mutates self.commitindex as a side effect.
1. Iterate candidate indices: Loop from commit_index + 1 up through the last log index. Each n is a candidate for the new commit index.
2. Term filter: Skip any entry whose term doesn't match currentterm. This is the safety guard — entries from old terms are never directly committed.
3. Count replicas: Start count at 1 (the leader always has the entry). For each peer, check if matchindex[peer] >= n. If so, that peer has replicated at least up to index n, so increment count.
4. Majority check: Compute total = len(peers) + 1 (full cluster size). If count > total // 2, a strict majority has replicated index n, so advance commitindex = n.
5. No early exit: The loop continues even after finding a committable index, because a higher index might also have a majority. The final value of commitindex after the loop is the highest such index.
self.commitindex — this is the only state change. Once advanced, followers learn the new commit index via the leader_commit field in subsequent AppendEntries RPCs.None. The method assumes all state is well-formed. There are no bounds checks, no exception handling, and no defensive guards. If matchindex contains a peer not in peerids, that peer is simply ignored (only peerids is iterated). If matchindex is missing a peer, dict.get(peer, 0) defaults to 0, treating that peer as having replicated nothing.
Called in two places:
1. tick() (line ~139) — on every heartbeat interval, after sending AppendEntries to all peers. This catches up the commit index periodically.
2. handleappendresponse() (line ~189) — immediately after a successful replication response updates matchindex. This allows the commit index to advance as soon as a majority is confirmed, without waiting for the next heartbeat.
Both callers gate on self._state == "leader", so this method is never invoked on followers or candidates.
self._log — indexed by integer position; relies on the sentinel at index 0 and contiguous indexing.self.matchindex — populated by handleappendresponse on successful replication and initialized to all-zeros in becomeleader.self.peerids — set at construction, assumed immutable (no membership changes).matchindex values are always valid log indices (never negative, never beyond the log length).self._log[n] access would break.commitindex is always less than or equal to lastlogindex(). If commit_index somehow exceeded the log length, range() would produce an empty sequence (safe but silent).raft-consensus/raft.py:handleappendresponse — The other call site for advancecommitindex; shows how match_index is updated and how log backtracking works on failureraft-consensus/raft.py:handleappendentries — The follower side: how leadercommit propagates the commit index to followers via min(leadercommit, lastlogindex)raft-figure-8-safety — The Raft paper's Figure 8 scenario explaining why leaders must not commit entries from prior terms; this is the exact bug the term-check guard preventsraft-consensus/test_raft.py — Test cases that exercise commit advancement, including partition/heal scenarios that test the majority-counting logicraft-consensus/raft.py:becomeleader — How matchindex and nextindex are initialized when a node wins an election, which directly affects the first call to advancecommit_indexraft-leader-only-commits-current-term — advancecommitindex skips log entries whose term differs from current_term, ensuring old-term entries are only committed indirectly when a current-term entry at a higher index reaches majorityraft-commit-index-monotonic — commitindex is only ever assigned values greater than its current value within advancecommitindex, because the loop starts at commit_index + 1 and only assigns forwardraft-commit-requires-strict-majority — An entry is committed when count > total // 2, which for a 5-node cluster means 3+ replicas (including the leader), matching Raft's quorum requirementraft-advance-commit-called-twice — advancecommit_index is invoked both on heartbeat ticks and on successful append responses, so the commit index can advance eagerly without waiting for the next heartbeat cycleraft-match-index-defaults-zero — Peers missing from matchindex are treated as having replicated nothing (dict.get(peer, 0)), which is safe because becomeleader initializes all peers to 0