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.

Previously

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

  1. Watch
  2. Try it
  3. Predict
  4. Capture
Bitcask recovery — scanning data files. 0 B of 5.40 GB scanned. Elapsed 0 ms.
DATA FILES ON DISK001.data · 2.00 GB · 524,288 records · no hint (full crawl) · full crawl001.data2.00 GB · 524,288 recNo .hint sidecar — the recovery scanner must crawl every record in this file to rebuild keydir entries.no hint · full crawlfull crawl002.data · 2.00 GB · 524,288 records · .hint present (metadata-only) · pending002.data2.00 GB · 524,288 rec.hint sidecar present — keydir is rebuilt from metadata only, no value scan..hint · metadata onlypending003.data (active) · 1.40 GB · 320,000 records · no hint (full crawl) · pending003.data …1.40 GB · 320,000 recNo .hint sidecar — the recovery scanner must crawl every record in this file to rebuild keydir entries.no hint · full crawlCRC mismatch — last record's checksum didn't match, tail is torn and truncatedCRC ✗ TRUNCATEDpendingKEYDIR (rebuilding)keyfileoffset0 rowsTIME TO READY0 msgreen = w/ hints (7.2 s)red = no hints (11.3 s)BYTES SCANNED0 B/ 5.40 GBdataset: 5.40 GBScanning 001 (full crawl)
What to watch for

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.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Bitcask.rebuild_keydir
the open() path that runs before the first GET can be served
def rebuild_keydir(data_dir):
keydir = {} # empty — RAM is gone
files = sorted(data_dir.list('*.data'))
for f in files:
hint = data_dir.path(f.id + '.hint')
if hint.exists():
entries = read_hints(hint) # metadata only
else:
entries = scan_data_file(f) # full crawl
for e in entries:
# later tstamp wins on overwrite
if 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
Bitcask.scan_data_file
full crawl with torn-tail truncation via per-record CRC
def scan_data_file(f):
pos = 0
while 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 tail
return # stop, log ends here
key = kv[:ksz]
vpos = pos + HEADER_SIZE + ksz
yield Entry(key, tstamp, vsz, vpos)
pos += HEADER_SIZE + ksz + vsz
OnDisk.record_formats
data records carry values; hint records omit them
# 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 file
key: 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

Built with Arqly
Every scene in Build a Bitcask-style KV store builds on the one before it.All 9 Build a Bitcask-style KV store scenes