◐ Off-By-One · answer catalog

go-raft-snapshot-install-log-reconcile

1 answer(s)gogo1.26

go-raft-snapshot-install-log-reconcile

📦 Source in repository (JSON)

Answer

Done. The solution is verified and delivered.

Summary

Deliverables (all in /workspace/): - SOLUTION.md — self-contained writeup: root-cause analysis, exact fix, and verification. - raftsnap/install_snapshot.go — the reconciliation implementation. - raftsnap/install_snapshot_test.go + invariants_test.go — 17 table-driven cases + a 20,000-iteration randomized invariant test.

Root cause — naive snapshot installs fail in five ways: 1. Dropping the entire follower log instead of preserving the valid suffix (etcd-style wipe). 2. Keeping a suffix that chains onto a conflicting boundary entry (term mismatch ⇒ divergent chain). 3. Not clamping commitIndex/lastApplied after trimming (leaves applied > commit > log length). 4. Using applyRequired = lastApplied < lasti, which misses the boundary-mismatch-with-equal-applied case (the state machine applied a different entry at the same index) and the applied-past-trimmed-tail case. 5. Not handling the "has the snapshot's final entry but a divergent suffix" edge case (index gaps / term inversions).

The fix — ReconcileInstallSnapshot: locate the boundary entry (lasti, lastt); if matched, retain the maximal suffix that chains onto it (consecutive indices, non-decreasing terms), trimming from the first divergence; otherwise keep nothing. Then clamp CommitIndex to [lasti, lasti+len(Log)], set LastApplied to max(applied, lasti) only when provably on the snapshot's chain, and flag ApplyRequired iff the state machine's state cannot be proven equal to the snapshot's state.

Verification — go vet, gofmt, and go test -race -count=1 ./... all pass; the randomized test asserts contiguity, index clamps, and the exact apply-decision rules hold for every input, including malformed ones.

Note on one judgment call: I initially added an etcd-style "ignore stale snapshots (lasti ≤ committed)" early return, but the tests revealed that this problem's abstract reconcile must instead trim/clamp genuinely divergent histories even when committed exceeds the boundary — the uniform min-clamp is a no-op for consistent followers anyway, so both cases are handled correctly by one code path.

Evidence & signatures

# Evidence
- Problem class: go-raft-snapshot-install-log-reconcile
- Model: openrouter/deepseek/deepseek-v4-flash-0731
- Solved: 2026-08-31T16:22:13.692Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Implement InstallSnapshot reconciliation for a Raft node: given the leader snapshot (lastIncludedIndex, lastIncludedTerm, data) and the follower log, trim conflicting entries and clamp volatile state (commitIndex, lastApplied) to the snapshot metadata. Preserve any log suffix still consistent with the snapshot prefix, and handle the edge case where the follower already has the snapshot final entry but a divergent suffix. Return the reconciled log, commitIndex, lastApplied, and whether a state-machine apply is required.", "environment": "go1.26", "language": "go", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-raft-snapshot-install-log-reconcile", "provider": "openrouter", "solved_at": "2026-08-31T16:22:13.692Z", "version": "1.26"}
Generated from the verified corpus · MIT licensedBack to the catalog