durability
Crash-consistent persistence primitives: directory abstraction, record logs, generic WAL, checkpoints, and recovery.
Quick start
use MemoryDirectory;
use ;
use Arc;
let dir: = new;
// Write
let mut w = new;
w.append.unwrap;
w.append.unwrap;
w.flush.unwrap;
// Recover
let records = new.replay.unwrap;
assert_eq!;
assert_eq!; // entry_id assigned by writer
WalWriter<E> and WalReader<E> are generic -- define your own entry type with
#[derive(Serialize, Deserialize)] and use WalWriter::<YourType>::new(dir).
Entry IDs are assigned by the writer and stored in the frame header, not in your payload.
Not Provided (and why)
- Multi-process locking: This crate does not manage
flockor IPC locks.- Recommendation: Use an external lock manager or a single-writer process architecture.
- Strong consistency by default:
writecalls are buffered.- Recommendation: Use
sync_allexplicitly when you need a durability barrier.
- Recommendation: Use
What really matters (failure model)
Most bugs in persistence layers come from being vague about failures. This crate is explicit about the failures it targets:
- Crash / torn writes: partial writes at the tail (e.g. process crash mid-record).
- Corruption detection: CRC/magic/version/type mismatches are treated as errors (even in “best-effort” modes).
- Stable storage vs “reported success”: unless you add explicit barriers, a successful write may still be only in OS caches.
Contract surface (what you get)
- Prefix property:
- Best-effort replay returns a prefix of the valid operation stream. It never returns “garbage” operations.
- Narrow best-effort scope:
- Best-effort tolerance applies only to the final WAL segment’s torn tail records, and also tolerates a torn header in the final segment (crash during segment creation).
- Corruption in non-final segments is an error.
- Deterministic checkpoints:
- Checkpoint payloads are written deterministically (stable ordering), to avoid nondeterministic churn.
Stable-storage durability (opt-in)
If you need “survives power loss after success”, you must add explicit barriers:
- Use
durability::storage::sync_file(dir, path)tosync_allthe file. - Use
durability::storage::sync_parent_dir(dir, path)to sync the parent directory when you rely on durable create/rename. - Prefer the first-class trait
durability::DurableDirectorywhen you want code to declare that it needs stable-storage durability operations (it providesatomic_write_durable/atomic_rename_durable). - For convenience:
WalWriter::flush_and_sync()RecordLogWriter::flush_and_sync()
If the backend cannot map paths to the OS filesystem (Directory::file_path() returns None), these operations return NotSupported.
Checkpoint publishing + WAL truncation (the “real lifecycle”)
If you want to truncate old WAL segments, do it in a crash-safe order:
- write a durable checkpoint,
- append
WalEntry::Checkpointto the WAL and make that durable, - only then delete WAL segments that are fully covered by the checkpoint.
Use CheckpointPublisher for this pattern.
After truncation, recovery should start from the latest checkpoint marker (see RecoveryManager::recover_latest).
Modules at a glance
storage:Directoryabstraction +FsDirectory/MemoryDirectory+ sync helpers.recordlog: generic append-only log with CRC framing + strict/best-effort replay.walog: generic multi-segment WAL (WalWriter<E>/WalReader<E>) underwal/with strict/best-effort replay andWalWriter::resumerepair. IncludesWalEntryfor segment-index use cases.checkpoint/checkpointing: checksumed snapshot files (postcard payloads).replay: generic record-log replay helpers (checkpoint + suffix pattern).recover: checkpoint + WAL recovery for a “segments + deletes” view.publish: crash-safe checkpoint publish + WAL truncation.
Running
- Tests:
cargo test - Heavier property runs:
PROPTEST_CASES=512 cargo test --test prop_wal_resume - Benches:
cargo bench
Fuzzing (opt-in)
Property tests are great for semantic invariants; fuzzing is great for “never panic on weird bytes”.
Recommended (if you have LLVM tooling available):
- Install:
cargo install cargo-fuzz - Run (examples; see
fuzz/):cargo fuzz run fuzz_wal_entry_decodecargo fuzz run fuzz_wal_dir_replaycargo fuzz run fuzz_checkpoint_readcargo fuzz run fuzz_recordlog_read