pub enum NarCodec {
Zstd,
Xz,
}Expand description
How a NAR is packed for the cache.
── ★ ONE VALUE — THE BYTES, THE SUFFIX AND THE NARINFO ALL DERIVE ──────
The codec used to be stated in THREE disconnected places in push_path:
the call to compress_xz, the literal .nar.xz in the URL, and
compression: "xz".to_string() in the narinfo. Three declarations of one
fact, free to disagree — and disagreement is not a cosmetic bug: a narinfo
that says xz over zstd bytes makes EVERY client fail to decompress, so
the cache would serve corruption while reporting success. That is the
failure mode this type removes, by leaving no way to state the codec twice.
── WHY zstd IS THE DEFAULT — MEASURED, NOT ASSUMED ───────────────────── Benchmarked on a real 48 MB NAR (git 2.51.2), 10 cores, 2026-08-05:
codec ms size %orig
xz -6 (previous) 8368 8 MB 17%
xz -6 -T0 7615 8 MB 17% <- multithreading xz buys 9%
zstd -19 -T0 11298 8 MB 17% <- SLOWER than xz for the ratio
zstd -12 -T0 440 10 MB 21% <- 19x faster than xz -6
zstd -9 -T0 243 10 MB 22% <- 34x fasterTwo beliefs died there. “Just add -T0 to xz” gains 9%, not the order of
magnitude it promises — liblzma’s block splitting barely engages at this
size. And zstd is only faster at lower levels; at -19 it loses to xz on
both axes. The knee is -12: 19x the speed for four percentage points of
ratio.
That trade is obviously right HERE and the reason is architectural: this cache is a LOCAL origin serving a handful of fleet nodes over tailscale. Bandwidth is cheap; CPU-hours on the fleet’s only x86_64-linux builder are not. MEASURED cost of the old default on rio 2026-08-05: a 2483-path closure spent FOUR HOURS in single-threaded xz, and because nix runs the post-build hook synchronously it blocked every build on that node — which is a different bug (fixed by detaching the hook) that this default made unsurvivable.
A mixed cache is fine and needs no migration: each narinfo declares its own
codec, so paths already stored as .nar.xz keep resolving while new pushes
land as .nar.zst.
Variants§
Zstd
zstd at [ZSTD_LEVEL], multithreaded. The default.
Xz
xz level 6 — what this cache used before 2026-08-05. Kept selectable rather than deleted (★★ MODULARIZE, DON’T DELETE): it is still the right choice for an origin that is bandwidth-bound rather than CPU-bound, and it is what every already-stored path is packed with.
Implementations§
Source§impl NarCodec
impl NarCodec
Sourcepub fn narinfo_name(self) -> &'static str
pub fn narinfo_name(self) -> &'static str
The Compression: field value. nix’s wire vocabulary, not ours —
verified 2026-08-05 by having nix write a zstd cache itself
(nix copy --to 'file://…?compression=zstd') and reading back what it
emitted.
Sourcepub fn url_suffix(self) -> &'static str
pub fn url_suffix(self) -> &'static str
The NAR URL suffix.
.nar.zst, NOT .nar.zstd — taken from nix’s own output in the same
experiment above. Guessing here would have produced a cache whose URLs
no client resolves, and nothing in our own types would have objected.
Sourcepub fn compress(self, data: &[u8]) -> Result<Vec<u8>, CacheError>
pub fn compress(self, data: &[u8]) -> Result<Vec<u8>, CacheError>
Compress a NAR under this codec.
zstd runs multithreaded across the machine’s cores; workers(0) asks
the library for one worker per core. A failure to enable threading is
deliberately NOT fatal — it costs speed, never correctness, and a cache
push that refuses to run is worse than a slow one.
Trait Implementations§
impl Copy for NarCodec
impl Eq for NarCodec
impl StructuralPartialEq for NarCodec
Auto Trait Implementations§
impl Freeze for NarCodec
impl RefUnwindSafe for NarCodec
impl Send for NarCodec
impl Sync for NarCodec
impl Unpin for NarCodec
impl UnsafeUnpin for NarCodec
impl UnwindSafe for NarCodec
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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