Skip to main content

Module read_stack

Module read_stack 

Source
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:

  1. The Ragnar stree is 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).
  2. 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 FourTables or [OneTableFourColumns]. Ordinal and column live in the same object and are replaced together.
  3. 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 absent and deliberately do NOT count: a have negotiation 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§

ObjectReadStack
The read stack: Ragnar stree → Arrow columns → redb, in one object.
RebuildTriggers
When the Arrow/stree projection is rebuilt from the redb tail. Either trigger fires; both are per repository and per generation.
StackStats
A snapshot of the stack’s counters. Everything here is applied output — rows that exist, lookups that happened — not configuration echoed back.

Enums§

RebuildReason
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 row was answered by the redb tail rather than by the projection.