Expand description
Ordered writes — durability and ordering without atomicity, for trees where every prefix is a legal state.
A ChangeSet buys all-or-nothing: a journal, rollback,
and a recovery precondition, because for the trees it serves a half-applied
set is illegal and must be impossible to observe. Not every tree is like
that. An append-only store — content-addressed blobs, operation logs, a
write-once history — declares every partially-written batch legal: files
that arrived without the record that names them are exactly what any
interrupted transfer leaves, and the format already has a name for that
state. Buying atomicity there pays for a journal to rule out states that
were never illegal, and worse, it plants that journal (and a
recover-before-read obligation) in a tree that never asked for either.
What such a tree does need is narrower, and this module is exactly it:
- Ordering. Nothing durable may name anything that is not durable yet. The record naming a payload must never survive a crash the payload did not — a store where it could would hold a name pointing at nothing, which is an illegal state.
- Durability, when asked for: once
applyreturns, the batch survives a power cut.
§The protocol
An OrderedBatch is tiers of writes separated by
barriers. Within a tier nothing is ordered —
payloads may land in any order, because nothing names them yet. At each
barrier, everything staged so far is ordered (Durability::Ordered)
ahead of everything after it: each tier’s files are flushed to the barrier
and each directory that gained an entry is flushed too, since a name in a
directory is its own write and persists separately from the bytes it
names. The final tier is flushed to whatever finality the caller asks —
Durable for “this write survives power loss”,
Ordered for “consistent, but the tail may be
lost with the crash that interrupted it”.
A crash therefore leaves some prefix of the barriers: every tier before the interruption whole and durable, the interrupted tier possibly partial, everything after it absent. For the store shaped as above, every one of those states is a state it already tolerates.
§What a partial tier can hold
The two op kinds degrade differently inside the interrupted tier, and the difference is the port’s, honestly inherited:
- A
writelands through the backend’s ownStorage::replace— its override included — so a crash shows the whole old file or the whole new one wherever the backend can promise that, and the documented degrade where it cannot. Its flush rides with the tier’s, like everything else here: the bytes are barriered inside the call, and the entry that publishes them is the directory flush’s to carry. - A
create_newis an exclusive create under its final name — decision-grade for concurrency (two writers racing to one name see one winner), but a crash mid-write can leave the newest tier’s file torn. A consumer whose names promise their contents (a digest-named blob) must be able to recognize and discard a torn file nothing names yet; the barrier guarantees the “nothing names it yet” half.
§What this does not do
No journal, no rollback, no recovery step, no stale-journal refusal. An
error mid-apply returns immediately and the tree holds a consistent
prefix — the same shape a crash leaves — for the caller to complete,
retry, or garbage-collect on its own terms. Single writer, like everything
in this crate: two appliers against one tree race, and exclusivity beyond
one create_new name is the caller’s to arrange.
Structs§
- Ordered
Batch - Tiers of writes separated by barriers, applied in order by
apply: within a tier nothing is ordered, across abarriereverything is.
Enums§
- BatchOp
- One staged op of an
OrderedBatch. Paths are root-relative, joined onto the root atapplytime, exactly asFileOp’s are.