pub enum NarCodec {
Zstd {
level: ZstdLevel,
},
Xz {
level: XzLevel,
},
}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.
── WHY THE LEVEL LIVES INSIDE THE VARIANT ────────────────────────────
The level used to be a free-standing const ZSTD_LEVEL. Lifting it to a
sibling config field ({ codec, level }) would have been the obvious move
and is wrong: level = 12 means nothing when codec = Xz, and level = 9
means two completely different things across the two codecs (near-max for
xz, mid-range for zstd). A pair whose second component is only meaningful
for some values of the first is a variant payload, not a field — so the
codec choice and its one tuning knob travel as one value that cannot be
split, reordered, or half-applied.
The levels are ZstdLevel / XzLevel, not bare integers: xz2’s
encoder panics on a preset above 9, so an out-of-range level in a config
file used to be a crash waiting on the first push. It is now rejected where
the value is built.
Variants§
Zstd
zstd, multithreaded. The prescribed default at
ZstdLevel::PRESCRIBED.
Xz
xz — 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.
The level is deliberately absent from this value: it is an encoder
setting, and a decompressor reads it out of the frame header. A codec
reconfigured from level 12 to level 3 still publishes zstd, and every
client still reads it.
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.
§Errors
CacheError::Io if the encoder cannot be built or the write fails.
The level cannot be the cause: it is bounded at construction.
Sourcepub fn decompress(self, data: &[u8]) -> Result<Vec<u8>, CacheError>
pub fn decompress(self, data: &[u8]) -> Result<Vec<u8>, CacheError>
Decompress a NAR packed under this codec.
The inverse of compress, on the same value — so a
test (or a future serve-side verifier) can prove that what a narinfo
declares actually decodes the bytes it points at, rather than
asserting two strings match.
§Errors
CacheError::Io if the data is not a valid frame for this codec.
Sourcepub fn from_narinfo_name(name: &str) -> Option<Self>
pub fn from_narinfo_name(name: &str) -> Option<Self>
Resolve a codec from the Compression: field of a narinfo — the
reader’s half of the one-value invariant.
Level is irrelevant on the decode side (it lives in the frame header),
so the returned value carries the prescribed level as a placeholder and
is only ever used for its decompress /
url_suffix projections.
Trait Implementations§
impl Copy for NarCodec
Source§impl<'de> Deserialize<'de> for NarCodec
impl<'de> Deserialize<'de> for NarCodec
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
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,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
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 moreSource§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);