pub trait NarRefIndex: Send + Sync {
// Required methods
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,
Self: 'async_trait;
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,
Self: 'async_trait;
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,
Self: 'async_trait;
}Expand description
The reverse index of a single StorageBackend.
Three verbs, all idempotent. Every implementation persists edges in the backend’s own store, so the index survives exactly as long as the data it describes.
§Which way to be wrong
Over-reporting a referrer keeps a NAR that could have been reclaimed — a leak. Under-reporting deletes a NAR another narinfo still advertises — an outage. Every implementation rounds toward over-reporting, and any place that cannot (a hot tier’s key expiring, a fan-out where one tier is down) says so at that site.
Required Methods§
Sourcefn 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,
Self: 'async_trait,
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,
Self: 'async_trait,
Record “hash’s narinfo advertises nar_path”. Idempotent.
§Errors
Propagates the backend’s write failure.
Sourcefn 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,
Self: 'async_trait,
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,
Self: 'async_trait,
Forget that edge. Idempotent — forgetting an absent edge is Ok(()).
§Errors
Propagates the backend’s delete failure.
Sourcefn 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,
Self: 'async_trait,
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,
Self: 'async_trait,
Every store-path hash whose narinfo advertises nar_path, sorted and
deduplicated.
§Errors
Propagates the backend’s read failure. An empty vector is a real answer
(“nothing advertises this NAR”), never a stand-in for a failed lookup —
which is why this returns Result<Vec<_>> and not Vec<_>.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".
Implementors§
impl NarRefIndex for LocalStorage
The reverse index as a directory tree: one empty file per edge.
record is a blind create of a path that names its own content, so it is
idempotent and two writers racing on the same edge simply write the same
empty file. Nothing here reads a set to write it back, which is why
concurrent narinfo pushes cannot lose an edge.
impl NarRefIndex for MemNarRefIndex
impl NarRefIndex for S3Storage
The reverse index as one zero-byte object per edge, under nar-refs/.
A LIST bounded by the edge prefix is the referrer set. Recording is a
blind PUT of a key that names its own content, so it is idempotent and two
concurrent pushes cannot lose an edge — which a read-modify-write of a
set-valued object could, and S3 gives no compare-and-swap to prevent it with.
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.
impl<C> NarRefIndex for PgStorageBackend<C>where
C: PgCacheConn + 'static,
The reverse index as one zero-value row per edge in sui_cache_nar_ref.
The key is the canonical NarRefKey, so the referrer lookup is a
primary-key range scan under NarRefScan rather than a table sweep.
impl<C> NarRefIndex for RedisBackend<C>where
C: RedisConn,
The reverse index as one Redis key per edge.
Edges carry no TTL, deliberately, even when narinfo/NAR writes do. An
edge that expired while the narinfo it describes is still live would be an
under-report, and an under-report authorizes deleting a NAR out from under
that narinfo. An edge that outlives its narinfo is an over-report, which
costs a retained NAR. The whole tier is still best-effort — Redis
maxmemory LRU can drop an edge regardless — which is why a
TieredBackend unions this tier’s answer with the
durable tiers’ rather than trusting it alone.