- Atomic snapshot save/load — write-to-temp +
fsync+ atomic rename + directoryfsync; an interrupted write never corrupts an existing good file. - Versioned header — magic bytes, format version, index-type tag, dim, metric, vector count. All sizes are fixed-width little-endian
u64, so a file is portable across 32- and 64-bit hosts. - CRC32 integrity — computed over the payload; a single-bit flip surfaces as
ChecksumMismatchon load, never a panic or a silently-wrong result. - Write-ahead log & crash recovery — with
wal_enabled, everyinsert/deleteis logged andfsynced before it touches memory, then replayed onto the snapshot onload. A crash mid-append leaves a torn tail that recovery detects and discards. - Generic over the index —
PersistedIndex<I: Index + Persistable>wraps any concrete index; the framing lives here, the payload bytes live in the index'sPersistableimpl.
Installation
[]
= "0.3"
= "1.0"
= "1.0"
Quick start
use ;
// `MyIndex: iqdb_index::Index + iqdb_persist::Persistable`
let cfg = new;
// Wrap an in-memory index and save it atomically.
let wrapped = open_with?;
wrapped.save?;
// Later — reconstruct it from disk (verifies magic, version, type, CRC32).
let restored: = load?;
let index = restored.index;
For durable, crash-recoverable mutation, set cfg.wal_enabled = true and
mutate through the wrapper — each op is logged before it is applied, and
checkpoint() folds the log back into a fresh snapshot:
let mut db = open_with?; // writes base snapshot + opens WAL
db.insert?; // logged + fsynced, then applied
db.delete?;
db.checkpoint?; // snapshot the state, truncate the WAL
// after a crash: PersistedIndex::load(cfg) replays the WAL onto the snapshot
An index opts in by implementing the two-method Persistable trait. A
complete, runnable version lives in
examples/save_and_load.rs — run it
with cargo run --example save_and_load. Full reference:
docs/API.md.
Status
This is v0.3.0: atomic snapshot save/load + versioned header + CRC32 (v0.2) and the write-ahead log with replay and crash recovery (v0.3) are implemented and tested. Zstd/LZ4 compression (v0.4) and the external storage-io substrate (v0.5+) land in later phases per the ROADMAP. The compression knob already exists and rejects cleanly (PersistError::Unsupported) until it ships.
Where It Fits
iqdb-persist is the embedded persistence crate of the iQDB family. It builds on:
iqdb-types— core vocabulary (DistanceMetric,IqdbError)iqdb-index— theIndex/IndexCoretraits it wraps as persistable
Snapshot file I/O goes through a tiny internal Storage seam so the future storage-io substrate (the fsys-rs rename) can drop in unchanged; v0.3 ships one impl over std::fs, and the WAL appends through its own std::fs handle until that substrate lands in v0.5.
Contributing
See dev/DIRECTIVES.md for engineering standards and the definition of done. Before a PR: cargo fmt --all, cargo clippy --all-targets --all-features -- -D warnings, and cargo test --all-features must be clean.