Expand description
The read path as one stack: Ragnar stree in front, the Arrow index tables
it points into, and the redb un-sealed tail it falls through to.
One read stack, not three components.
A git object lookup in this crate goes through a single stack with three surfaces, and the whole point of putting them in one type is that they cannot be reasoned about — or drift — separately:
- The Ragnar
streeis the fast path. It is not a cache in front of an index; it is the index’s key surface,crate::oid_index’s 8-byte-prefix static B-tree, reached through whichever [ObjectIndex] arm the stack was parameterised with. No third oid index is written here (LAW 5). - The Arrow tables are what it points into. The stree resolves an oid to
an ordinal; the ordinal addresses the five facts in the Arrow IPC columns
of
FourTablesor [OneTableFourColumns]. Ordinal and column live in the same object and are replaced together. - redb is what it falls through to, and redb always answers. Every object
ever appended is in redb. The stree+Arrow surface is a projection of a
prefix of it, rebuilt on a trigger. So a stree miss is never an answer — it
is a routing decision, and the row is fetched from redb. The single
exception is not an exception to the rule but a proof of it: when the
projection covers every row, “the stree missed” and “the repository
does not have it” are the same statement, and the trip is skipped. See
[
ObjectReadStack::projection_is_complete].
This is skade’s arrangement copied
rather than reinvented: StaticIndex (STree64 over snapshot ids) in front,
the redb commit log behind, resolve_many doing one pipelined tree pass and
then one redb transaction for the slots the tree did not fill. The names
differ because the keys differ (oids, not snapshot ids); the shape does not.
§Scope: one stack per REPOSITORY
Not per account. A git negotiation — have/want, the push connectivity
check — is scoped to one repository, and an oid means nothing outside the
repository that stores it. [ObjectReadStack::open] therefore takes the path
of one repository’s tail database.
§Why the projection may be incomplete and can still be trusted
The archive is append-only. An object’s five facts are written once and
never rewritten — [ObjectReadStack::append] refuses a rewrite that would
change them rather than accepting it (that refusal is what makes the rest of
this true, and it is asserted in
[tests::an_append_only_violation_is_refused]). Given that:
- a hit in the stree is always valid — the row it points at cannot have become stale, because nothing may change it;
- a miss carries no information at all — it means “not in this projection”, which is not the same as “not in this repository”;
- so the projection is allowed to lag, and the only cost of lag is the redb fall-through on the rows it has not absorbed yet.
That asymmetry is the entire reason a trigger is sound where a rebuild-per-write would be ruinous.
§The rebuild trigger
Two triggers, both in [RebuildTriggers], either one fires:
- misses — lookups the stree missed and redb answered, against a
threshold relative to the size of the projection. Genuine absences are
counted separately in
absentand deliberately do NOT count: ahavenegotiation is mostly misses, and if those drove the trigger, a busy read-only repository would rebuild for ever without a single new object having arrived. - volume — stored object bytes appended since the last rebuild.
Defaults, and the measurements they were derived from, are on
[RebuildTriggers::DEFAULT_TAIL_HITS_PER_ROW],
[RebuildTriggers::DEFAULT_MIN_TAIL_HITS] and
[RebuildTriggers::DEFAULT_TAIL_BYTES].
§Threads
There are none here. A rebuild runs inline on the append
that trips the trigger, or on an explicit rebuild.
skade rebuilds on a detached thread; the sanctioned home for a detached thread
in this constellation is gatling::background::Job (LAW 3 — rayon is banned
and so is a hand-rolled pool), and Job is join-required with no completion
probe, so “in the background” would mean picking an arbitrary later call to
block on. Inline on a trigger is deterministic and, with the defaults below,
rare. An owner who wants the overlap can drive rebuild
from a Job itself — it takes &self and swaps under an RwLock.
Structs§
- Object
Read Stack - The read stack: Ragnar stree → Arrow columns → redb, in one object.
- Rebuild
Triggers - When the Arrow/stree projection is rebuilt from the redb tail. Either trigger fires; both are per repository and per generation.
- Stack
Stats - A snapshot of the stack’s counters. Everything here is applied output — rows that exist, lookups that happened — not configuration echoed back.
Enums§
- Rebuild
Reason - Which threshold fired, with the value that fired it. Returned rather than logged, so a caller can record why a rebuild happened.
Constants§
- TAIL_
ORDINAL_ BIT - Set on the ordinal of every row that came from the tail rather than from the Arrow projection.
Functions§
- is_
tail_ row - True when
rowwas answered by the redb tail rather than by the projection.