Crash recovery — scan or hint
On startup the keydir is gone, so Bitcask scans every data file end-to-end to rebuild it; hint files (a merge byproduct) are metadata-only and drop cold-start time by roughly 10x.
Merge produced a hint file as a byproduct. That byproduct is the difference between a 30-minute restart and a 3-minute one — and the per-record CRC is what makes the surviving log parseable in either case.
Scene 06
Crash recovery — scan or hint
- Watch
- Try it
- Predict
- Capture
The process was killed mid-write. Watch the startup scanner sweep each file: 001 has no hint sibling so it crawls record-by-record, 002 has a .hint file from merge so it flies through metadata only, and 003 (the active file) hits a CRC mismatch on its torn tail and truncates to the last valid offset. The keydir rebuilds on the right, with later writes overwriting earlier ones for the same key. Every record carries a per-record CRC32 — that's why the surviving log is always parseable even after a hard crash.
Highlighted lines are the ones running in the diagram right now.
def rebuild_keydir(data_dir):keydir = {} # empty — RAM is gonefiles = sorted(data_dir.list('*.data'))for f in files:hint = data_dir.path(f.id + '.hint')if hint.exists():entries = read_hints(hint) # metadata onlyelse:entries = scan_data_file(f) # full crawlfor e in entries:# later tstamp wins on overwriteif e.key not in keydir or e.tstamp > keydir[e.key].tstamp:keydir[e.key] = (f.id, e.vsz, e.vpos, e.tstamp)return keydir
def scan_data_file(f):pos = 0while pos < f.size:header = f.pread(pos, HEADER_SIZE)crc, tstamp, ksz, vsz = unpack(header)kv = f.pread(pos + HEADER_SIZE, ksz + vsz)if crc32(header[4:] + kv) != crc:f.truncate(pos) # drop torn tailreturn # stop, log ends herekey = kv[:ksz]vpos = pos + HEADER_SIZE + kszyield Entry(key, tstamp, vsz, vpos)pos += HEADER_SIZE + ksz + vsz
# Data record — written by every put() to the active file:data_record = struct(crc: u32, # checksum over (tstamp..value)tstamp: u64,ksz: u32,vsz: u32,key: bytes[ksz],value: bytes[vsz], # the value bytes themselves)# Hint record — written by merge alongside each merged data file:hint_record = struct(tstamp: u64,ksz: u32,vsz: u32,vpos: u64, # where the value lives in the .data filekey: bytes[ksz],# no value bytes — that is the entire point)
Where this sits in Build a Bitcask-style KV store
Scene 06 of 9, in the Crash & sync act — Recovery, hint files, fsync — pick two.. On startup the keydir is gone (RAM is gone). Hint files (a merge byproduct) drop cold-start time ~10x; per-record CRCs make a torn tail self-truncating.
Up next. CRCs make the log self-truncating, so the surviving file is always parseable. The question that opens up next: how recent is the surviving tail?
All 9 scenes in Build a Bitcask-style KV store · Every curriculum