Skip to main content

RebuildTriggers

Struct RebuildTriggers 

Source
pub struct RebuildTriggers {
    pub tail_hits_per_row: f64,
    pub min_tail_hits: u64,
    pub tail_bytes: u64,
    pub fold_ratio: f64,
}
Expand description

When the Arrow/stree projection is rebuilt from the redb tail. Either trigger fires; both are per repository and per generation.

Fields§

§tail_hits_per_row: f64

The miss trigger, as a multiple of the rows already in the projection. See DEFAULT_TAIL_HITS_PER_ROW for why it is relative and not a constant.

§min_tail_hits: u64

Floor and off-switch for the miss trigger: the threshold is never below this, and 0 disables the miss trigger entirely whatever tail_hits_per_row says.

§tail_bytes: u64

Rebuild once this many stored object bytes have been appended since the last rebuild. 0 disables the volume trigger. On a persisting stack this is the floor of the volume threshold; see fold_ratio.

§fold_ratio: f64

On a persisting stack, the volume threshold scales with the index: a fold fires once the object bytes appended since the last fold reach fold_ratio × the persisted index’s own size (never below tail_bytes). 0.0 leaves the fixed floor alone. See DEFAULT_FOLD_RATIO for why the value is 1. Ignored on a stack that does not persist — there a rebuild writes nothing and the fixed 64 MiB is the right cadence.

Implementations§

Source§

impl RebuildTriggers

Source

pub const DEFAULT_TAIL_HITS_PER_ROW: f64 = 1.0

1.0 — one tail-served lookup per row in the projection.

The miss trigger is relative because the two costs it balances scale differently, and a single constant is therefore wrong at one end or the other. Both costs MEASURED on oden 2026-08-07, 1-min loadavg 2.1–2.2, examples/read_stack_bench.rs, sha1 oids, batch 1000, 3 runs per cell, worst run-to-run spread 24.9%:

100 000 objects1 000 000 objects
lookup the projection answers80 ns221 ns
lookup the tail answers463 ns937 ns
the fall-through costs383 ns716 ns
rebuild (ordered tail scan + projection build)40.9 ms547.9 ms
per row in the repository409 ns548 ns
break-even, tail-served lookups per row1.070.77

The rebuild has paid for itself once the fall-through has carried roughly one lookup per row, at both sizes and an order of magnitude apart — which is why the ratio is the right shape for this trigger and 1.0 is the right value in it. A fixed 4096 would rebuild a million-object repository (0.55 s) to save 4096 × 716 ns ≈ 2.9 ms — 190× the wrong way.

Re-measure if the tail engine or the projection build changes: this is a ratio of two measured costs and nothing else.

Source

pub const DEFAULT_MIN_TAIL_HITS: u64 = 4096

4096 — the floor under the relative threshold.

On an empty or nearly-empty projection the ratio above is ~0 and would rebuild on the first miss, over and over, during exactly the period when objects are still arriving. 4096 misses is ~1.6 ms of fall-through at the measured 100 000-object price — cheap enough to be worth waiting for on any repository, and enough that a burst of small pushes coalesces into one rebuild.

Source

pub const DEFAULT_TAIL_BYTES: u64

64 MiB of appended object bytes.

A git push is a packfile, and the pack is the unit that lands in the tail. 64 MiB is comfortably more than one ordinary push and less than a big one, so a busy repository rebuilds on the order of once per large push rather than once per push — while a repository taking a 2 GiB initial import rebuilds ~32 times over that import instead of once at the end, which is what keeps the fall-through from carrying the whole import. It is the trigger that carries the normal case; the miss trigger is the backstop for a repository that is read hard and written rarely.

Source

pub const DEFAULT_FOLD_RATIO: f64 = 1.0

1.0 — fold when the bytes appended since the last fold equal the index’s own size.

Since 2026-09-04 a rebuild on a persisting stack WRITES the projection (objects.index), so the cadence above stopped being free. MEASURED on the kernel mirror with the fixed 64 MiB floor alone: an 8.72 GB import folded ≈136 times, each rewriting the whole growing file — ≈51 GB of sequential writes for a 747 MB index (INFERRED from the arithmetic; ≥18 distinct file sizes OBSERVED at 10 s sampling). The ack did not slow on oden’s NVMe; on the appliance’s disks it would.

A ratio of 1 gives a bound that is easy to say and holds by construction: the fold’s total writes never exceed the push’s own bytes. Each fold writes an index roughly the size of the bytes that triggered it, so the sum of index writes over an import is ≈ the bytes imported — ~8.7 GB for the kernel instead of ~51, and the number of folds falls from ~136 to O(log n). The price is the redb tail’s high-water mark: with the index at 53.5 B/object and pack bytes at ~620 B/object, “appended bytes = index bytes” is ≈ 8.6 % of the folded rows in redb before a fold, i.e. ~1.2 M rows ≈ 250 MB of redb rows on the kernel mirror (redb allocates in doublings, so a ~512 MiB file) against 270 MB with the floor alone. Re-measure on the rig after changing either number; both are in .nornir/storage-levers-2026-09-03.md.

Source

pub fn miss_threshold(&self, projection_rows: u64) -> Option<u64>

The miss threshold for a projection of projection_rows rows, or None when the miss trigger is off.

Source

pub fn manual() -> Self

Both triggers off: the projection is rebuilt only when asked.

Trait Implementations§

Source§

impl Clone for RebuildTriggers

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for RebuildTriggers

Source§

impl Debug for RebuildTriggers

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for RebuildTriggers

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl PartialEq for RebuildTriggers

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for RebuildTriggers

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Allocation for T
where T: RefUnwindSafe + Send + Sync,

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more