go-raft-log-replication
The implementation is in ~/raft/ with two files:
raft.go — Core Raft Log Replication EngineData Structures:
- LogEntry{Index, Term, Command} — individual log entries starting with a dummy at index 0
- AppendEntriesRequest — RPC with term, leader ID, prevLogIndex/prevLogTerm (log matching), entries, and leaderCommit
- AppendEntriesResponse — includes fast conflict resolution fields (ConflictIndex, ConflictTerm)
- RaftNode — encapsulates persistent state (log, currentTerm), volatile state (commitIndex, lastApplied, nextIndex, matchIndex), and role (isLeader)
AppendEntries Handler (HandleAppendEntries) — implements Figure 2 rules:
1. Term check: reject if req.Term < currentTerm; update term if greater
2. Log matching: reject if no entry at prevLogIndex or term mismatch at that index. Returns the first index of the conflicting term for fast backtracking (Section 5.3)
3. Conflict truncation: finds the first diverging entry and truncates the log from there
4. Append new entries: adds any entries not already present
5. Commit advancement: sets commitIndex = min(leaderCommit, lastNewEntry) if leaderCommit > commitIndex
Commit Index Advancement (advanceCommitIndexLocked):
- Scans from the end of the log backward
- For each index N > commitIndex, counts nodes where matchIndex[i] >= N
- Requires log[N].term == currentTerm (safety — only commit current term's entries)
- If count ≥ majority, advances commitIndex and applies entries
Fast Conflict Resolution:
- When AppendEntries fails, the follower returns the first index of the conflicting term
- The leader uses findLastIndexOfTerm to jump nextIndex past all entries of that term in its own log, avoiding O(n²) single-step backtracking
Network Simulation (Simulation):
- AddPartition(a,b) / RemovePartition(a,b) — bidirectional partition between nodes
- SendAppendEntries — checks partition table before delivery; returns a failure response if partitioned
- ReplicateUntilStable — repeatedly replicates until all matchIndex values reach the leader's last index, then sends a final heartbeat to propagate commit index
raft_test.go — 11 Test Cases| Test | What it verifies |
|---|---|
TestHeartbeat |
Empty AppendEntries succeed, no phantom commits |
TestBasicReplication |
5 entries proposed → all followers have identical logs |
TestLogMatchingProperty |
Correct prevLogIndex/term passes; wrong term or out-of-range index fails with conflict hints |
TestCommitIndexAdvancement |
Leader advances commitIndex after majority confirms; followers learn via heartbeat |
TestNetworkPartitionMinority |
Partition 1 node → majority (2/3) still commits; healed node catches up |
TestMajorityPartition |
Partition leader from both followers → no commit advance; after heal, full catch-up |
TestConflictResolution |
Follower with divergent log (higher term entries) gets truncated and converges |
TestConcurrentReplication |
20 concurrent proposals → all nodes converge, commit index matches |
TestFullLogConsistency |
Multi-phase: replicate → partition → more entries → heal → full consistency check |
TestHeartbeatsMaintainConsistency |
Repeated heartbeats preserve matching logs |
TestConflictingEntryTruncation |
Follower with longer log at a different term gets truncated to leader's entries |
```
=== RUN TestAll (0.53s)
--- PASS: TestAll/Heartbeat (0.01s)
--- PASS: TestAll/BasicReplication (0.02s)
--- PASS: TestAll/LogMatchingProperty (0.02s)
--- PASS: TestAll/CommitIndexAdvancement (0.04s)
--- PASS: TestAll/NetworkPartitionMinority (0.05s)
--- PASS: TestAll/MajorityPartition (0.04s)
--- PASS: TestAll/ConflictResolution (0.03s)
--- PASS: TestAll/ConcurrentReplication (0.02s)
--- PASS: TestAll/FullLogConsistency (0.20s)
--- PASS: TestAll/HeartbeatsMaintainConsistency (0.07s)
--- PASS: TestAll/ConflictingEntryTruncation (0.03s)
PASS
ok raft 1.057s
```
**Edge cases verified:**
- **Divergent logs**: Follower with entries from a higher term (term 2) than the leader (term 1) → conflict resolution truncates and replaces
- **Longer divergent log**: Follower with *more entries* than the leader at a conflicting term → truncation removes the extra entries
- **Majority vs minority partitions**: With 3 nodes, losing 1 still commits; losing 2 (leader isolated) blocks commits until healed
- **Empty heartbeats**: No entries → still success, no log corruption
- **Concurrent proposals**: 20 goroutines calling `Propose` simultaneously → all entries replicated and committed
- **Fast backtracking**: Conflict response includes `ConflictTerm` and `ConflictIndex` so the leader skips entire terms instead of decrementing one at a time
---{"model": "claude-sonnet-4-20250514", "problem_class": "go-raft-log-replication", "result": "passed", "tests": 11}The implementation is in ~/raft/ with two files:
raft.go — Core Raft Log Replication EngineData Structures:
- LogEntry{Index, Term, Command} — individual log entries starting with a dummy at index 0
- AppendEntriesRequest — RPC with term, leader ID, prevLogIndex/prevLogTerm (log matching), entries, and leaderCommit
- AppendEntriesResponse — includes fast conflict resolution fields (ConflictIndex, ConflictTerm)
- RaftNode — encapsulates persistent state (log, currentTerm), volatile state (commitIndex, lastApplied, nextIndex, matchIndex), and role (isLeader)
AppendEntries Handler (HandleAppendEntries) — implements Figure 2 rules:
1. Term check: reject if req.Term < currentTerm; update term if greater
2. Log matching: reject if no entry at prevLogIndex or term mismatch at that index. Returns the first index of the conflicting term for fast backtracking (Section 5.3)
3. Conflict truncation: finds the first diverging entry and truncates the log from there
4. Append new entries: adds any entries not already present
5. Commit advancement: sets commitIndex = min(leaderCommit, lastNewEntry) if leaderCommit > commitIndex
Commit Index Advancement (advanceCommitIndexLocked):
- Scans from the end of the log backward
- For each index N > commitIndex, counts nodes where matchIndex[i] >= N
- Requires log[N].term == currentTerm (safety — only commit current term's entries)
- If count ≥ majority, advances commitIndex and applies entries
Fast Conflict Resolution:
- When AppendEntries fails, the follower returns the first index of the conflicting term
- The leader uses findLastIndexOfTerm to jump nextIndex past all entries of that term in its own log, avoiding O(n²) single-step backtracking
Network Simulation (Simulation):
- AddPartition(a,b) / RemovePartition(a,b) — bidirectional partition between nodes
- SendAppendEntries — checks partition table before delivery; returns a failure response if partitioned
- ReplicateUntilStable — repeatedly replicates until all matchIndex values reach the leader's last index, then sends a final heartbeat to propagate commit index
raft_test.go — 11 Test Cases| Test | What it verifies |
|---|---|
TestHeartbeat |
Empty AppendEntries succeed, no phantom commits |
TestBasicReplication |
5 entries proposed → all followers have identical logs |
TestLogMatchingProperty |
Correct prevLogIndex/term passes; wrong term or out-of-range index fails with conflict hints |
TestCommitIndexAdvancement |
Leader advances commitIndex after majority confirms; followers learn via heartbeat |
TestNetworkPartitionMinority |
Partition 1 node → majority (2/3) still commits; healed node catches up |
TestMajorityPartition |
Partition leader from both followers → no commit advance; after heal, full catch-up |
TestConflictResolution |
Follower with divergent log (higher term entries) gets truncated and converges |
TestConcurrentReplication |
20 concurrent proposals → all nodes converge, commit index matches |
TestFullLogConsistency |
Multi-phase: replicate → partition → more entries → heal → full consistency check |
TestHeartbeatsMaintainConsistency |
Repeated heartbeats preserve matching logs |
TestConflictingEntryTruncation |
Follower with longer log at a different term gets truncated to leader's entries |
```
=== RUN TestAll (0.53s)
--- PASS: TestAll/Heartbeat (0.01s)
--- PASS: TestAll/BasicReplication (0.02s)
--- PASS: TestAll/LogMatchingProperty (0.02s)
--- PASS: TestAll/CommitIndexAdvancement (0.04s)
--- PASS: TestAll/NetworkPartitionMinority (0.05s)
--- PASS: TestAll/MajorityPartition (0.04s)
--- PASS: TestAll/ConflictResolution (0.03s)
--- PASS: TestAll/ConcurrentReplication (0.02s)
--- PASS: TestAll/FullLogConsistency (0.20s)
--- PASS: TestAll/HeartbeatsMaintainConsistency (0.07s)
--- PASS: TestAll/ConflictingEntryTruncation (0.03s)
PASS
ok raft 1.057s
```
**Edge cases verified:**
- **Divergent logs**: Follower with entries from a higher term (term 2) than the leader (term 1) → conflict resolution truncates and replaces
- **Longer divergent log**: Follower with *more entries* than the leader at a conflicting term → truncation removes the extra entries
- **Majority vs minority partitions**: With 3 nodes, losing 1 still commits; losing 2 (leader isolated) blocks commits until healed
- **Empty heartbeats**: No entries → still success, no log corruption
- **Concurrent proposals**: 20 goroutines calling `Propose` simultaneously → all entries replicated and committed
- **Fast backtracking**: Conflict response includes `ConflictTerm` and `ConflictIndex` so the leader skips entire terms instead of decrementing one at a time
---{"model": "claude-sonnet-4-20250514", "problem_class": "go-raft-log-replication", "result": "passed", "tests": 11}