Skip to main content

TieredBackend

Struct TieredBackend 

Source
pub struct TieredBackend { /* private fields */ }
Expand description

Three-tier read-through / write-through cache resolver.

Holds Arc<dyn StorageBackend> per tier, so any backend composes — the real deployment injects RedisBackend (L1), PgStorageBackend (L2), S3Storage (L3); tests inject in-memory mocks + a LocalStorage.

Implementations§

Source§

impl TieredBackend

Source

pub fn new( l1: Arc<dyn StorageBackend>, l2: Arc<dyn StorageBackend>, l3: Arc<dyn StorageBackend>, ) -> TieredBackend

Compose three tiers with the default WritePolicy::WriteThrough.

Source

pub fn with_write_policy( l1: Arc<dyn StorageBackend>, l2: Arc<dyn StorageBackend>, l3: Arc<dyn StorageBackend>, write_policy: WritePolicy, ) -> TieredBackend

Compose three tiers with an explicit WritePolicy.

Source

pub fn write_policy(&self) -> WritePolicy

The active write policy.

Trait Implementations§

Source§

impl NarRefIndex for TieredBackend

The composite reverse index: each tier keeps its own edges, and the answer is their union.

§Why the union, and why it includes L1

list_narinfos reads only the authoritative tiers because a hot tier’s partial view would under-report a listing. Here the asymmetry runs the other way: an extra referrer keeps a NAR that could have been reclaimed (a leak), a missing one deletes a NAR another narinfo still advertises (an outage). So every tier that answers is believed — including a stale L1 edge that outlived its narinfo.

A tier whose read fails is not silently treated as empty: the failure is logged and propagated, because “this tier is down” must never resolve to “nobody advertises this NAR” for something about to delete.

Source§

fn record<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, nar_path: &'life1 str, hash: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, TieredBackend: 'async_trait,

Record on both durable tiers (gated by durable_write_outcome) and best-effort on L1 — the same shape as a narinfo write, so an edge lands wherever its narinfo does.

Source§

fn forget<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, nar_path: &'life1 str, hash: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, TieredBackend: 'async_trait,

Forget on every tier, best-effort.

A tier that keeps an edge it should have dropped over-reports, which retains a NAR — the safe direction, and the reason this does not abort the fan-out on the first failure.

Source§

fn referrers<'life0, 'life1, 'async_trait>( &'life0 self, nar_path: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Vec<String>, StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, TieredBackend: 'async_trait,

Every store-path hash whose narinfo advertises nar_path, sorted and deduplicated. Read more
Source§

impl StorageBackend for TieredBackend

Source§

fn nar_residency(&self) -> NarResidency

The composite’s residency is its weakest tier’s — a streaming resolver in front of a whole-value tier still materializes NARs. Reporting Streaming here because the resolver itself streams would be exactly the rounding-up this type exists to prevent.

Source§

fn get_nar_stream<'life0, 'life1, 'async_trait>( &'life0 self, path: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<Pin<Box<dyn Stream<Item = Result<Bytes, StoreError>> + Send>>>, StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, TieredBackend: 'async_trait,

Fall through the tiers and promote, without ever holding the NAR.

Identical resolution order and identical promotion targets to get_narinfo; the only difference is that a promotion re-reads the tier that answered (see [TierNarSource]) instead of copying a buffer the caller is holding. The stream handed back is opened against the tier that actually had the content, so the caller’s bytes never depend on whether a warm succeeded.

Source§

fn delete_narinfo_record<'life0, 'life1, 'async_trait>( &'life0 self, hash: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, TieredBackend: 'async_trait,

Fan the narinfo removal out to every tier, then report whether every tier actually removed it.

§Why this is not best-effort, unlike the old delete

Reads fall through, so a narinfo that ANY tier still holds is still served. If this swallowed a per-tier failure and returned Ok(()), the composed delete would go on to drop the edge and remove the NAR — leaving a narinfo that is still served, advertising a NAR that is gone. That is the strand, arrived at from the other side.

Every tier is still attempted (one dead tier does not stop the others), but a failure surfaces, so delete stops before touching the NAR. The cost is that a GC pass against a degraded tier aborts instead of half-completing — the right trade: a retained NAR is a leak, a stranded narinfo is an outage.

Source§

fn delete_nar_record<'life0, 'life1, 'async_trait>( &'life0 self, nar_path: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, TieredBackend: 'async_trait,

Fan the NAR removal out to every tier and report any failure.

A surviving copy on one tier is a leak, not an outage — but it is still not a completed delete, and a caller that believes the bytes are gone (a byte-accounting sweep) would drift.

Source§

fn put_nar_stream<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, path: &'life1 str, src: &'life2 dyn NarSource, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, TieredBackend: 'async_trait,

Fan a NAR out to the tiers in the same order as before, from a re-openable source.

Line for line the previous put_nar, with data: &[u8] replaced by src: &dyn NarSource and each tier opening its own bounded stream. That equivalence is the reason NarSource is re-openable rather than a one-shot Stream: a one-shot stream can be consumed once, so it would force either buffering the NAR to fan it out (the bug) or interleaving chunks across tiers (a different order). The load-bearing properties are unchanged:

  • L2 and L3 are both attempted, then gated by durable_write_outcome;
  • the L1 warm happens strictly after that gate under WriteThrough (strictly before it under WriteBack), and its result is discarded, so a refused L1 warm can never fail a build.
Source§

fn wipe_all<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<usize, StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, TieredBackend: 'async_trait,

Fan the wipe out to EVERY tier (L1 hot + L2/L3 durable), so a cache-wipe clears all three at once. Best-effort per tier (mirrors delete): one tier’s failure is logged, never aborting the others — the whole point is to return the store to cold. Reports the largest per-tier narinfo count removed (the authoritative tiers’ full set).

Source§

fn get_narinfo<'life0, 'life1, 'async_trait>( &'life0 self, hash: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<String>, StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, TieredBackend: 'async_trait,

Retrieve narinfo text by store path hash.
Source§

fn get_nar<'life0, 'life1, 'async_trait>( &'life0 self, path: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<Vec<u8>>, StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, TieredBackend: 'async_trait,

Retrieve a NAR blob by its relative path. Read more
Source§

fn put_narinfo_record<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, hash: &'life1 str, content: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, TieredBackend: 'async_trait,

Store narinfo text keyed by store path hash — the record verb: one key, no index maintenance. Read more
Source§

fn nar_ref_index(&self) -> &dyn NarRefIndex

This backend’s narhash → store-hash reverse index. Read more
Source§

fn put_nar<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, path: &'life1 str, data: &'life2 [u8], ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, TieredBackend: 'async_trait,

Store a NAR blob at the given relative path. Read more
Source§

fn list_narinfos<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Vec<String>, StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, TieredBackend: 'async_trait,

List all stored narinfo hashes.
Source§

fn put_narinfo<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, hash: &'life1 str, content: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Store narinfo text and record the reverse edge it creates. Read more
Source§

fn advertised_nar<'life0, 'life1, 'async_trait>( &'life0 self, hash: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<String>, StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait,

The NAR path this store path’s narinfo advertises — resolved, never guessed. Read more
Source§

fn delete<'life0, 'life1, 'async_trait>( &'life0 self, hash: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait,

Delete a store path’s narinfo, and its NAR only if nothing else advertises that NAR. Read more
Source§

fn reindex_nar_refs<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<usize, StoreError>> + Send + 'async_trait>>
where 'life0: 'async_trait, Self: 'async_trait,

Rebuild every reverse edge from the narinfos this backend holds, and return the number of edges recorded. Read more

Auto Trait Implementations§

Blanket Implementations§

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> 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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

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

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
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, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

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