Skip to content
Snapshots
step 1/5

Reading — step 1 of 5

Read

~2 min readProduction

Snapshots

Your log grows without bound: every set ever accepted, kept forever. Three things eventually break — disk fills up, restart replay time grows linearly (a node replaying 100M entries is down for hours), and a brand-new follower must ingest the whole history just to join. The fix is log compaction by snapshot: persist the state machine's current image, remember exactly where it stands, and discard the log prefix it covers.

A snapshot is three things:

snapshot = {
  state:                the full KV map as of some applied index
  last_included_index:  the last log index the image covers
  last_included_term:   the TERM of the entry at that index
}

Why store the term? Because the consistency check never sleeps. The first AppendEntries after the boundary carries prev_log_index = last_included_index — and with that entry deleted from the log, the only way to verify prev_log_term is the value saved in the snapshot. Drop it and you cannot validate the very first RPC after compaction.

Snapshot only committed, applied state. A snapshot is an irreversible apply: you can truncate a log suffix when a new leader tells you to, but you cannot un-bake data out of a state-machine image. If you snapshot uncommitted entries and a new leader then overwrites them (the Figure-8 scenario), your image contains writes that officially never happened — permanent divergence. Real implementations snapshot at (or below) last_applied, which never passes commit_index.

Absolute vs. array indexing — the implementation trap. After discarding entries 1..K, the entry at array position 0 is Raft index K+1. Every log access now goes through the offset (last_included_index). One forgotten conversion is the classic source of off-by-one corruption in student Raft implementations — which is why your exercise reports indices absolutely.

Catching up a hopeless straggler. If a follower needs entries the leader has already discarded (next_index <= last_included_index), AppendEntries cannot help — the data is gone. The leader ships the whole image via the InstallSnapshot RPC (chunked; snapshots can be gigabytes), the follower swaps in the state and truncates its log, and ordinary replication resumes after the boundary.

Operationally: snapshot on a threshold (etcd defaults to every 10,000 applied entries), and take the image without stalling the Raft loop — fork()-based copy-on-write or a COW data structure, as etcd's bbolt and CockroachDB's Pebble do.

Your exercise

Implement SNAPSHOT over a growing log. The grader-caught mistake: snapshotting nothing. With commit_index = 0 there is no committed prefix, so after APPEND 1 a then SNAPSHOT, SNAPSHOT_INFO must still print none. Indices stay ABSOLUTE across truncation: snapshot at commit 2, APPEND two more entries, COMMIT_INDEX 4, SNAPSHOT — now LOG_LEN prints 1 and SNAPSHOT_INFO prints last_idx=4 last_term=2. And last_term is the term of the entry AT the commit index: with terms 1,2,3 and COMMIT_INDEX 2, expect last_idx=2 last_term=2.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…