unknown
The implementation consists of a complete Raft log compaction and InstallSnapshot RPC mechanism in Go. The code is split into two files:
raft_snapshot.go — Core implementation; raft_snapshot_test.go — 8 unit tests covering all invariants.
┌─────────────────────────────────────────────────────┐
│ Leader │
│ │
│ checkSnapshotThreshold() ───► Snapshot() │
│ │ └─ persistSnapshot() │
│ │ trims log, clamps │
│ │ commit/lastApplied │
│ ▼ │
│ leaderSendSnapshotIfNeeded(server) │
│ │ │
│ ├─ nextIndex ≤ snapshotIndex? │
│ │ └─ yes → sendInstallSnapshot(server) │
│ └─ no → sendAppendEntries(server) │
│ │
│ sendInstallSnapshot: builds InstallSnapshotArgs, │
│ calls RPC, on success updates nextIndex/matchIndex │
└──────────────────────┬──────────────────────────────┘
│ InstallSnapshot RPC
▼
┌─────────────────────────────────────────────────────┐
│ Follower │
│ │
│ InstallSnapshot handler: │
│ 1. Reject if args.Term < currentTerm │
│ 2. Step down if args.Term > currentTerm │
│ 3. Ignore if snapshot already covered │
│ 4. Find overlap in log at lastIncludedIndex/term │
│ ├─ match → keep trailing entries │
│ └─ no match → discard entire log │
│ 5. Save snapshot, clamp commit/lastApplied │
│ 6. Send ApplyMsg{SnapshotValid: true} to applyCh │
│ 7. Persist state │
└─────────────────────────────────────────────────────┘
Log indexing: rf.log[0] is a placeholder entry whose Index/Term equal the snapshot's LastIncludedIndex/LastIncludedTerm. Real entries start at rf.log[1]. A helper logIdx(idx) converts external index → slice offset.
Snapshot creation: When the log exceeds SnapshotThreshold (1000 entries) past the last snapshot, Snapshot() is called with the serialised state-machine state. It persists the snapshot, trims the log (keeping only entries after the snapshot), and clamps commitIndex / lastApplied to at least the snapshot index.
InstallSnapshot RPC: Sent by the leader to a follower whose nextIndex ≤ snapshot.LastIncludedIndex. The handler on the follower side:
Sends the snapshot data to the state machine via applyCh
Chunking: The implementation shows the pattern for single-shot snapshots. For multi-chunk, you add Offset to InstallSnapshotArgs, loop over the snapshot data, and set Done = true only on the final chunk.
| Edge Case | How It's Handled |
|---|---|
| Snapshot index > last log index | Rejected — no-op |
| Obsolete snapshot (older than existing) | Rejected — lastIncludedIndex ≤ rf.snapshot.LastIncludedIndex check |
| Follower already has the snapshot | Ignored via the same check |
| Stale leader (lower term) | Handler returns with reply.Term = currentTerm, leader steps down |
| New leader with lagging follower | leaderSendSnapshotIfNeeded detects nextIndex ≤ snapshotIndex and sends InstallSnapshot |
| Consistent log tail after snapshot | Overlap search: if lastIncludedIndex/term match a log entry, trailing entries are preserved |
| Inconsistent log tail | Entire log discarded (replaced with placeholder) |
| Concurrent snapshot + log append | Mutex (rf.mu) ensures atomicity of all snapshot operations |
All 8 tests pass:
```
=== RUN TestSnapshotCreation
PASS: Snapshot correctly truncated log and preserved entries after the snapshot.
--- PASS: TestSnapshotCreation (0.00s)
=== RUN TestSnapshotRejectedWhenIndexTooHigh
PASS: Snapshot rejected when index exceeds last log index.
--- PASS: TestSnapshotRejectedWhenIndexTooHigh (0.00s)
=== RUN TestObsoleteSnapshotIgnored
PASS: Obsolete snapshot did not overwrite a more recent one.
--- PASS: TestObsoleteSnapshotIgnored (0.00s)
=== RUN TestFollowerInstallSnapshot
PASS: Follower applied InstallSnapshot, log replaced, applyCh received snapshot.
--- PASS: TestFollowerInstallSnapshot (0.00s)
=== RUN TestFollowerIgnoresDuplicateSnapshot
PASS: Duplicate snapshot was correctly ignored.
--- PASS: TestFollowerIgnoresDuplicateSnapshot (0.00s)
=== RUN TestFollowerRejectsStaleTerm
PASS: Stale InstallSnapshot from lower-term leader was rejected.
--- PASS: TestFollowerRejectsStaleTerm (0.00s)
=== RUN TestLeaderSendsSnapshotToLaggingFollower
PASS: Leader correctly identified lagging follower and triggered InstallSnapshot.
--- PASS: TestLeaderSendsSnapshotToLaggingFollower (0.00s)
=== RUN TestFollowerPreservesConsistentTail
PASS: Follower preserved consistent log tail after installing snapshot at index 2.
--- PASS: TestFollowerPreservesConsistentTail (0.00s)
PASS
ok raft 0.003s
```
### Edge Cases Tested
- **TestSnapshotCreation**: 2000-entry log, snapshot at index 1000 → log trimmed to 1001 entries, placeholder at index 1000, entries 1001..2000 preserved
- **TestSnapshotRejectedWhenIndexTooHigh**: Snapshot at index 200 with only 100 entries → rejected
- **TestObsoleteSnapshotIgnored**: Snapshot at 300, then older snapshot at 200 → ignored
- **TestFollowerInstallSnapshot**: Full snapshot replaces entire 50-entry log, commitIndex/lastApplied clamped, `applyCh` receives snapshot message
- **TestFollowerIgnoresDuplicateSnapshot**: Follower already has snapshot at index 100 → duplicate ignored
- **TestFollowerRejectsStaleTerm**: Leader term 3, follower term 5 → rejected, reply.Term = 5
- **TestLeaderSendsSnapshotToLaggingFollower**: Leader has snapshot at index 5, follower nextIndex = 1 → triggers InstallSnapshot
- **TestFollowerPreservesConsistentTail**: Snapshot at index 2/term 1 matches log entry, trailing entries (3,4,5) preserved
---{"model": "deepseek-v4-flash", "result": "completed"}