<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>AlternativeBit</title>
    <link>https://alternativebit.fr/tags/guix/</link>
    <description>Recent posts</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 01 Jun 2026 10:24:00 +0200</lastBuildDate>
    <atom:link href="https://alternativebit.fr/tags/guix/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Nar-klepto: Guix and Nix Offline Cache</title>
      <link>https://alternativebit.fr/posts/nix-and-guix-offline-hacking/</link>
      <author>picnoir</author>
      <category domain="https://alternativebit.fr/tags/nix">Nix</category><category domain="https://alternativebit.fr/tags/guix">Guix</category>
      <pubDate>Mon, 01 Jun 2026 10:24:00 +0200</pubDate>
      <guid>https://alternativebit.fr/posts/nix-and-guix-offline-hacking/</guid>
      <description>&lt;p&gt;TL;DR: introducing &lt;a href=&#34;https://codeberg.org/picnoir/nar-klepto&#34;&gt;nar-klepto&lt;/a&gt;, the context that led to its inception, and some fun experiments we did last week with it.&lt;/p&gt;
&lt;p&gt;4 years ago, I moved out of Bayonne to a rural area. My quality of life almost instantly skyrocketed; however, I quickly felt a bit frustrated that I could only discuss computer-related things via online video meetings.&lt;/p&gt;
&lt;p&gt;After the 2024 FOSDEM, I realized that if there&amp;rsquo;s no nerds around, why not invite my online friends at my place? And, for the last three years, I&amp;rsquo;ve been renting a very nice place close to where I live for a full week. I invite my favorite online friends there. Each year, a few strangers join, friends of friends, usually becoming new friends by the end of the week.&lt;/p&gt;
&lt;p&gt;This is nice, it makes me happy every time.&lt;/p&gt;
&lt;p&gt;I just came back from this year&amp;rsquo;s edition. Physically a bit exhausted, but mentally fully energized. A full week of nice discussions, drone and RC glider flying, multiplayer videogames, cooking, eating a bit too much, hot spring water, and bike rides!&lt;/p&gt;
&lt;p&gt;The place where we do this is super nice, and pretty remote… and… do not come with any internet connection. The owners do not seem to care at all, it&amp;rsquo;s their fun vacation house, no need for internet there! We&amp;rsquo;re probably the only group renting this and caring about any internet connectivity at the same time. We&amp;rsquo;re probably terminally online.&lt;/p&gt;
&lt;p&gt;The only uplink we have comes from a pretty terrible LTE router&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. And it&amp;rsquo;s not great. If you&amp;rsquo;re into Guix or NixOS, you probably already see the issue. These distributions are amazing. But boy oh boy, they&amp;rsquo;re network hungry. And boy oh boy, they dislike not being able to reach to their binary caches. Last year, I spent some time creating a solution for this, and it looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./cache.webp&#34; alt=&#34;old japanese laptop running nar clepto. A SSD is attached to it. The computer is plugged to a ethernet switch.&#34;&gt;&lt;/p&gt;
&lt;p&gt;This is a wonderful retro Panasonic CF-SZ5, coupled with an external USB SSD. Turned on, serving NARs 24/7 for a week. Poor thing. This used to be a pretty cheap setup, the SSD probably cost 3 times more than the laptop it&amp;rsquo;s attached to these days.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Always use obsolete hardware running bleeding-edge software.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I love the attitude and what it entails: making some effort to extend the lifespan of rare resources stored in these magic machines. Nice hardware setup to boast about, but that&amp;rsquo;s not really the point of this article. The software it runs is the interesting bit: &lt;a href=&#34;https://codeberg.org/picnoir/nar-klepto&#34;&gt;nar-klepto&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a collection of 3 programs able to fully backup a Guix or Nixpkgs release. Each and every derivation part of it. That&amp;rsquo;s ~100GB worth of compressed NARs for a Guix release, ~500GB for a Nix one.&lt;/p&gt;
&lt;p&gt;Conceptually speaking, you feed it a list of store paths, and it downloads the relevant narinfos and nar. You can retrieve this list of paths for a particular release using the data service for Guix, Hydra for NixOS. The narinfos are stored in a PostgreSQL DB, the nars on a filesystem. Finally, it comes with an HTTP server, able to send the NARs and narinfos to the Nix and Guix clients. It also provides a NixOS module to set up the server part of it and a Nginx reverse caching proxy. The reverse caching proxy comes in handy when people start to download packages not available in the cache. It could happen if an important security release forces people to upgrade their systems to a new Guix/Nix revision. The reverse proxy cache saves some of the metered LTE bandwidth and some substitution time.&lt;/p&gt;
&lt;p&gt;Downloading a full release comes at a cost for the project&amp;rsquo;s infrastructure, especially for Guix, which does not have a sponsored CDN. This tool is wasteful by nature and is not meant for recurring usage. It&amp;rsquo;s mostly useful for camps, retreats, and conference organisers. That&amp;rsquo;s an adequate tool to provide a one-off cache for a Guix/Nix event, a pretty inadequate one to host in your personal infra and mirror each and every Guix/Nix release.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://no%C3%A9.eu/&#34;&gt;Noé Lopez&lt;/a&gt; packaged it for Guix. He has been the first external contributor&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; to the project, adding the ability for the cache to advertize itself to the guix clients via zeroconf. Quite a nice feature, I&amp;rsquo;m a bit jaleous not to have it on NixOS! A nice coincidence happened though, adisbladis ended up releasing a similar project for NixOS, with a quite different design the same week: &lt;a href=&#34;https://github.com/adisbladis/nix-cache-beacon&#34;&gt;nix-cache-beacon&lt;/a&gt;. I might add the cache-beacon advertisement directly in nar-klepto in the future.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m pretty happy and proud of releasing this tool. Took me a good while… Close to a year. Last year, a good chunk of my online friends and colleagues jumped onto the vibe coding bandwagon. I was intrigued and did not want to miss out, so I joined the hype through this project. It did not work really well for me, and I have not used these tools since then: the experience was not enjoyable for me, and the result, while working, was a bit too trashy for my taste. There was a lot of unnecessary code duplication, each duplicate being slightly different from the others. I dreaded and procrastinated refactoring the thing for a good year, but I finally bit the bullet two weeks ago, cleaned and rewrote a good chunk of it. I enjoyed the process of desloppifying the project, it was fun. That&amp;rsquo;s good for me, who knows, maybe this will become my job at some point.&lt;/p&gt;
&lt;p&gt;I commissioned &lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; some artwork from lassulus. The second iteration was the charm! The overall sketchy vibe nicely fits this project&amp;rsquo;s story.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./nar_klepto.png&#34; alt=&#34;nar klepto logo&#34;&gt;&lt;/p&gt;
&lt;p&gt;During the geek event, I had to download a few things from the cache, and the experience was amazing. The substitution is fast, really fast. That&amp;rsquo;s a combination of two factors:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The narinfos are reachable with a very low latency, &amp;gt;3ms, from the cache. It noticeably speeds up cold evaluation.&lt;/li&gt;
&lt;li&gt;The NARS are available on the lan at a pretty high download speed. Sadly, not saturating a gigabit link. Despite how much andir- loves it, the glinet router needs an upgrade!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At some point, we paired with &lt;a href=&#34;https://flokli.de&#34;&gt;flokli&lt;/a&gt;, and tried to ingest the Guix release into a snix store to see what kind of storage gains we could get from chunking the Nars. &lt;a href=&#34;https://im-in.space/@flokli/116619174693467386&#34;&gt;Quite a lot it turns out!&lt;/a&gt;. The 115GB Guix nar collection ended up being ingested into a 21GB snix store. Quite impressive! We also tried to ingest the Nix Nar collection, but the gains were not as jaw-dropping. We&amp;rsquo;re not sure what explains this Guix efficiency. Maybe the lzip -&amp;gt; ztsd compression change, maybe there are some very repeated chunks in the cache. We&amp;rsquo;ll have to investigate more to figure this out.&lt;/p&gt;
&lt;p&gt;All this makes me believe there are ways to improve the substitution story of Guix/Nix. Something I&amp;rsquo;d like to focus a bit on in the near future ;)&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;GL-iNet X300. The hardware is ok, but the software is terrible. A fork of an outdated openwrt revision, a terribly buggy LTE model integration, and, of course, not updated for more than a year. It&amp;rsquo;s not even cheap, don&amp;rsquo;t buy that! Buy anything that can run mainline OpenWRT instead.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;to be fully honest, he was also part of the retreat and was depending on the cache. Lesson learned: a captive audience is a good bootstrap contributors pool!&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;without paying him, sadly. Times are tough for artists!&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    <item>
      <title>Nix Substitution: the Way Forward</title>
      <link>https://alternativebit.fr/posts/nixos/future-of-nix-substitution/</link>
      <author>picnoir</author>
      <category domain="https://alternativebit.fr/tags/nix">Nix</category><category domain="https://alternativebit.fr/tags/guix">Guix</category>
      <pubDate>Thu, 31 Mar 2022 08:18:00 +0200</pubDate>
      <guid>https://alternativebit.fr/posts/nixos/future-of-nix-substitution/</guid>
      <description>&lt;h2 id=&#34;abstract&#34;&gt;Abstract&lt;/h2&gt;
&lt;p&gt;Nix and Guix have the unfortunate reputation to require a lot of bandwidth to distribute software. This reputation is sadly grounded. It seems like explicitly pointing to your dependencies comes with an overhead cost in terms of download size. Or does it?&lt;/p&gt;
&lt;p&gt;Currently, both Guix and Nix can substitute pre-built store paths from a binary cache using the Nix Archive (NAR) format. After conducting several benchmarks, we conclude that the substitution granularity should be finer to improve the substitution performance. We can increase the substitution granularity either by substituting files individually or by substituting chunked individual files.&lt;/p&gt;
&lt;p&gt;If you are already familiar with the Nix and Guix substitution mechanism, feel free to ignore the next two sections and directly jump to the &lt;a href=&#34;#rethinking-the-substitution-granularity&#34;&gt;&amp;ldquo;Rethinking The Substitution Granularity&amp;rdquo;&lt;/a&gt; one.&lt;/p&gt;
&lt;h2 id=&#34;you-said-substitution&#34;&gt;You Said Substitution?&lt;/h2&gt;
&lt;p&gt;We usually don&amp;rsquo;t want to rebuild the whole world locally. When we want to install Firefox on a laptop, we&amp;rsquo;d rather not build it from the source on the machine itself but instead download a pre-built version from a cache. For this purpose, Nix provides a store path substitution mechanism.&lt;/p&gt;
&lt;p&gt;Instead of building a derivation locally, we substitute the local build with a pre-built artifact stored in a binary cache. The store paths are getting transferred between the binary cache and the local machine using the NAR archive format. This format can be seen as a compressed store path. Meaning we&amp;rsquo;ll have a NAR archive per top-level store path we&amp;rsquo;d like to substitute.&lt;/p&gt;
&lt;p&gt;A NAR is always identified using a hash derived from its content. NARs are content-addressed.&lt;/p&gt;
&lt;p&gt;In contrast to that, both the Nix/Guix derivations and the store paths they generate are input-addressed&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;: their hashes are directly derived from their dependencies hashes.&lt;/p&gt;
&lt;p&gt;You now see the issue: on one hand, we have input-addressed derivations and store paths, on the other hand, content-addressed NARs.&lt;/p&gt;
&lt;p&gt;Starting from an input-addressed derivation hash, how are we supposed to figure out the name of the content-addressed NAR we want to download? In other words, how can we figure out the output hash of a derivation without building it first?&lt;/p&gt;
&lt;p&gt;Introducing the &lt;strong&gt;NARinfo&lt;/strong&gt; file. Let&amp;rsquo;s take an example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;» curl https://cache.nixos.org/r5sjd57x0r07bwgipryaxqdkx1gglhiy.narinfo
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;StorePath: /nix/store/r5sjd57x0r07bwgipryaxqdkx1gglhiy-libidn2-2.3.2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;URL: nar/010zh5j819qyz6zai6h2hn3d7i406g00m2g4g62bq7m8g91553yb.nar.xz
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Compression: xz
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;FileHash: sha256:010zh5j819qyz6zai6h2hn3d7i406g00m2g4g62bq7m8g91553yb
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;FileSize: 63248
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;NarHash: sha256:1npw0jz1cw4k9x25f2vsdhsa5cf9568j46bpz2768a1izqj5n9lf
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;NarSize: 260816
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;References: 0z7sqj4pilbqyp45ix5b0mdgn9xlb024-libunistring-0.9.10 r5sjd57x0r07bwgipryaxqdkx1gglhiy-libidn2-2.3.2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Deriver: 0n91syjwrhmng41f8d23ad0sl4a6ic4g-libidn2-2.3.2.drv
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Sig: cache.nixos.org-1:G9PxMT0/nd9ELwL3BBeqWtb2ohMiqw4T4FUwFlAU9M0E45mKg77BzL9gJQ3wH4oYtsa61MV5uzNLPFvK8ZbyCA==
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To substitute the path &lt;code&gt;/nix/store/r5sjd57x0r07bwgipryaxqdkx1gglhiy-libidn2-2.3.2&lt;/code&gt; from the &lt;code&gt;cache.nixos.org&lt;/code&gt; binary cache, we&amp;rsquo;ll first have to download its associated NARinfo file. That NARinfo file is addressed using the same input-addressed hash as its associated store path: &lt;code&gt;r5sjd57x0r07bwgipryaxqdkx1gglhiy&lt;/code&gt;. The NARinfo will contain some metadata needed to perform the substitution, but also, and most importantly, the URL from which we can download the content-addressed NAR.&lt;/p&gt;
&lt;p&gt;To sum up, to perform a substitution, the Nix/Guix client will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Compute the input-addressed store path of the derivation.&lt;/li&gt;
&lt;li&gt;Fetch the associated &lt;code&gt;.narinfo&lt;/code&gt; from the binary cache it wants to substitute from.&lt;/li&gt;
&lt;li&gt;Download the content-addressed NARs referenced by the &lt;code&gt;.narinfo&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Unpack the downloaded NARs to the local Nix store.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;substitution-hell&#34;&gt;Substitution Hell&lt;/h2&gt;
&lt;p&gt;Now that we established how the Nix substitution mechanism works, let&amp;rsquo;s have a thought experiment: what will happen if we bump the version of a derivation living deep in the Nixpkgs/Guix dependency tree? Something like &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;curl&lt;/code&gt; store path being input-addressed, its name will change for sure. Likewise, its dependencies are also input-addressed, their name will also change. Overall, the &lt;code&gt;curl&lt;/code&gt; store path change will propagate throughout all the Nixpkgs/Guix dependency graph.&lt;/p&gt;
&lt;p&gt;You might say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hey! That&amp;rsquo;s not a big deal.&lt;/p&gt;
&lt;p&gt;The NARs are content addressed, this change of input hash shouldn&amp;rsquo;t propagate there.&lt;/p&gt;
&lt;p&gt;The binary cache might have to re-build the whole dependency graph, however, outside of curl, the content of the derivations should remain the same. So, in the end, most of the NARs should remain the same. The clients shouldn&amp;rsquo;t have to re-download all the store paths depending on curl.&lt;/p&gt;
&lt;p&gt;We won&amp;rsquo;t have to re-download massive derivations like Firefox and Chromium for a simple curl bump.&lt;/p&gt;
&lt;p&gt;Right?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And you&amp;rsquo;d be right except for one thing: in the Nix/Guix model, everything is explicit. Every time you&amp;rsquo;ll refer to the curl binary, you&amp;rsquo;ll have to explicitly refer to its store path &lt;code&gt;/nix/${inputHash}-curl/bin/curl&lt;/code&gt;. Its &lt;strong&gt;input-addressed&lt;/strong&gt; store path! This sneaky reference will manage to propagate the input-adressed instability to the output-addressed NARs.&lt;/p&gt;
&lt;p&gt;Coming back to your example: both Chromium and Firefox depend on curl. You&amp;rsquo;ll have to re-download both of these massive store path for every curl bump. Hopefully, you won&amp;rsquo;t mind downloading a couple of gigabytes for a sub-kB string change? Do you? :P&lt;/p&gt;
&lt;p&gt;One has to think: could we improve this situation?&lt;/p&gt;
&lt;h2 id=&#34;rethinking-the-substitution-granularity&#34;&gt;Rethinking the Substitution Granularity&lt;/h2&gt;
&lt;p&gt;Content addressing the substitution archives is definitely a good idea. It brings some hash stability to this unstable input-addressed scheme. However, as we saw earlier, because of the store path references, the input-addressed hash instability ends up propagating through the substitution mechanism.&lt;/p&gt;
&lt;p&gt;To improve the substitution stability, we could think about two solutions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Remove the store references from the NAR and replace them on the fly when decompressing the NAR into the actual Nix/Guix store.&lt;/li&gt;
&lt;li&gt;Increase the substitution granularity.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&amp;rsquo;m going to ignore that first solution for this post and focus on the &amp;ldquo;Increase the substitution granularity&amp;rdquo; solution.&lt;/p&gt;
&lt;p&gt;The Nix and Guix communities came up with two solutions based on this finer granularity substitution idea: &lt;a href=&#34;https://github.com/flokli/nix-casync/&#34;&gt;nix-casync&lt;/a&gt; and a file-based substitution discussed in the &lt;a href=&#34;https://lists.gnu.org/archive/html/guix-devel/2021-01/msg00079.html&#34;&gt;Guix mailing list&lt;/a&gt;.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Living in the countryside, my only Internet connection is a weak LTE link. I have 3 NixOS and 1 Guix system at home. Any mass-rebuild takes me at least 2 hours to substitute on a good day.&lt;/p&gt;
&lt;p&gt;Needless to say, I have a personal urge to improve that situation!&lt;/p&gt;
&lt;p&gt;Before blindly stepping into what looks like a multi-year-long sustained implementation effort, I wanted to gather some hard data showing how much we could gain from increasing the substitution granularity. It&amp;rsquo;s benchmarking time.&lt;/p&gt;
&lt;h2 id=&#34;benchmarking-fine-granularity-substitution-mechanisms&#34;&gt;Benchmarking Fine Granularity Substitution Mechanisms&lt;/h2&gt;
&lt;p&gt;For various scenarios, we&amp;rsquo;re going to build a Nix closure for 2 Nixpkgs commits. Then, we&amp;rsquo;ll simulate an update from the first commit to the second one and see how much data we&amp;rsquo;ll need to download from the binary cache.&lt;/p&gt;
&lt;p&gt;For each scenario, we&amp;rsquo;re going to evaluate the 4 following substitution techniques:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Nar substitution&lt;/strong&gt;: This is the substitution model currently used by the NixOS and Guix projects, it&amp;rsquo;ll act as our metrics baseline. It consists in archiving and compressing a full store path. In this benchmark, we&amp;rsquo;ll identify each NAR by its filename derived from the &lt;code&gt;sha256sum&lt;/code&gt; of its content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Casync substitution&lt;/strong&gt;: This is an experimental substitution mechanism implemented via the &lt;a href=&#34;https://github.com/flokli/nix-casync&#34;&gt;nix-casync&lt;/a&gt; project. Starting from a NAR, we decompress it, serialize all the files in a single stream, and chunk it into smaller bits before compressing them again. In this benchmark, we&amp;rsquo;ll identify each casync chunk by its filename, itself already derived from the &lt;code&gt;sha256&lt;/code&gt; sum of its content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File-based substitution&lt;/strong&gt;: This is the substitution method &lt;a href=&#34;https://lists.gnu.org/archive/html/guix-devel/2021-01/msg00079.html&#34;&gt;the Guix&lt;/a&gt; project brainstormed around. Each store gets substituted separately. In this benchmark, we&amp;rsquo;ll identify these files using the &lt;code&gt;sha256&lt;/code&gt; sum of their content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XZ-Compressed File-based substitution&lt;/strong&gt;: similar to the File-based substitution except each file is individually compressed using the xz compression mechanism (profile 6 extreme).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you&amp;rsquo;re interested in the nitty-gritty details, the source code and the raw data behind these benchmarks live &lt;a href=&#34;https://github.com/PicNoir/nix-guix-substitution-benchmark&#34;&gt;in this git repository&lt;/a&gt;. You can also have a look at the &lt;a href=&#34;https://alternativebit.fr/assets/substitution-benchmark-jupyter-notebook.html&#34;&gt;Jupyter notebook I used to crunch the benchmarks data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note: we&amp;rsquo;re using NixOS/Nixpkgs for all these benchmarks. However, provided Guix currently uses the same substitution mechanism, you can safely assume the same conclusions holds for it as well.&lt;/p&gt;
&lt;h3 id=&#34;mass-rebuild&#34;&gt;Mass Rebuild&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s kill the suspense right away and have a look at the most interesting scenario first. We&amp;rsquo;re going to simulate a NixOS closure update after the &lt;a href=&#34;https://github.com/NixOS/nixpkgs/pull/148396&#34;&gt;staging-next 2021-12-03&lt;/a&gt; merge containing, among other things, a &lt;code&gt;curl&lt;/code&gt; update. As we already established in the &amp;ldquo;&lt;a href=&#34;#substitution-hell&#34;&gt;Substitution Hell&lt;/a&gt;&amp;rdquo; section, a curl bump will propagate through the whole NixOS dependency graph. That&amp;rsquo;s the worst-case scenario in terms of substitution: the mass rebuild.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to use a &lt;a href=&#34;https://github.com/PicNoir/nix-guix-substitution-benchmark/blob/314f0e9fed0245161861ea333f737f6d9d28b955/machine.nix&#34;&gt;NixOS machine description&lt;/a&gt; depending on several diverse language stacks (C, Python, BEAM) and simulate a closure update after this mass rebuild.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see how the four substitution techniques perform:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/mass-rebuild-diagram.svg&#34; alt=&#34;Mass Rebuild Diagram&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;386.06&lt;/td&gt;
					&lt;td&gt;372.99&lt;/td&gt;
					&lt;td&gt;13.0702&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;608.652&lt;/td&gt;
					&lt;td&gt;192.195&lt;/td&gt;
					&lt;td&gt;416.457&lt;/td&gt;
					&lt;td&gt;48.4719&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;File&lt;/td&gt;
					&lt;td&gt;1652.19&lt;/td&gt;
					&lt;td&gt;705.665&lt;/td&gt;
					&lt;td&gt;973.348&lt;/td&gt;
					&lt;td&gt;-89.1915&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;476.523&lt;/td&gt;
					&lt;td&gt;229.489&lt;/td&gt;
					&lt;td&gt;247.034&lt;/td&gt;
					&lt;td&gt;38.4732&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;First, the good news: increasing the substitution granularity definitively works! The casync-based substitution almost cuts by half the amount of data we need to download for this mass rebuild.&lt;/p&gt;
&lt;p&gt;YAY!&lt;/p&gt;
&lt;p&gt;The xz-compressed file-based substitution sits in the same ballpark, saving us ~40% worth of data to download.&lt;/p&gt;
&lt;p&gt;We can already see two other things from this experiment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Without proper compression, the file-based substitution performs about half as good as the NAR substitution.&lt;/li&gt;
&lt;li&gt;Looking at the closure size of the various substitution methods, we can realize that the finer the substitution granularity gets, the worse the compression algorithms are performing. In other words, the massive gain in terms of re-used data/downloaded data ratio (~75% for casync) does not directly translate into download savings. We probably want to spend some time investigating the state-of-the-art WRT. compression algorithm specialized in small files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Provided these results, I&amp;rsquo;ll omit the uncompressed file substitution-related data for the next benchmarks: it&amp;rsquo;s consistently worse than the compressed counterpart in terms of performance and hurts the readability of the diagrams.&lt;/p&gt;
&lt;h3 id=&#34;channel-jump&#34;&gt;Channel Jump&lt;/h3&gt;
&lt;p&gt;In this scenario, provided the &lt;a href=&#34;https://github.com/NinjaTrappeur/nix-guix-substitution-benchmark/blob/314f0e9fed0245161861ea333f737f6d9d28b955/machine.nix&#34;&gt;same NixOS machine closure&lt;/a&gt; we used in the Mass Rebuild scenario, we&amp;rsquo;ll simulate a jump from the NixOS-21.11 stable channel to NixOS-unstable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/channel-jump-diagram.svg&#34; alt=&#34;Channel Jump Diagram&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;387.458&lt;/td&gt;
					&lt;td&gt;375.167&lt;/td&gt;
					&lt;td&gt;12.2905&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;610.886&lt;/td&gt;
					&lt;td&gt;310.541&lt;/td&gt;
					&lt;td&gt;300.345&lt;/td&gt;
					&lt;td&gt;17.2261&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;478.12&lt;/td&gt;
					&lt;td&gt;307.479&lt;/td&gt;
					&lt;td&gt;170.642&lt;/td&gt;
					&lt;td&gt;18.0423&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I did not expect much savings here, but surprisingly enough, we&amp;rsquo;ll shave about 18% worth of downloaded data using the compressed file-based substitution or casync.&lt;/p&gt;
&lt;p&gt;This scenario does not make total sense from a pragmatic point of view as-is: you rarely jump a NixOS machine closure from a stable to unstable NixOS channel. It however does make sense in the context of a dev machine. You often have to jump between different projects having their own dev &lt;code&gt;nix-shell&lt;/code&gt; closures pointing to different Nixpkgs channels.&lt;/p&gt;
&lt;p&gt;The good and unexpected news is: even in that scenario, we can expect some substantial gains in terms of the amount of data we&amp;rsquo;ll need to download!&lt;/p&gt;
&lt;h3 id=&#34;single-derivation-bump&#34;&gt;Single Derivation Bump&lt;/h3&gt;
&lt;p&gt;Now, one may ask: can we expect any savings for a single derivation substitution?&lt;/p&gt;
&lt;p&gt;Ludovic Courtès recently &lt;a href=&#34;https://lists.gnu.org/archive/html/guix-devel/2020-12/msg00258.html&#34;&gt;posted a summary of his findings&lt;/a&gt; on that matter in the Guix mailing list for various binaries containing various amounts of binary data and text.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s replicate this experiment with our benchmark. We&amp;rsquo;re going to simulate a version bump for Firefox, Gimp, Emacs, and OpenMPI. To do so, we&amp;rsquo;ll build the two derivations before and after the version bump, and from there see how much data we&amp;rsquo;ll be able to re-use and how much data we&amp;rsquo;ll need to download.&lt;/p&gt;
&lt;h4 id=&#34;firefox&#34;&gt;Firefox&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/firefox-bump.svg&#34; alt=&#34;Firefox bump results bar chart diagram. See the table below for a screen reader accessible alternative&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;219.352&lt;/td&gt;
					&lt;td&gt;56.4315&lt;/td&gt;
					&lt;td&gt;162.92&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;342.925&lt;/td&gt;
					&lt;td&gt;55.871&lt;/td&gt;
					&lt;td&gt;287.054&lt;/td&gt;
					&lt;td&gt;0.993342&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;260.424&lt;/td&gt;
					&lt;td&gt;55.8295&lt;/td&gt;
					&lt;td&gt;204.594&lt;/td&gt;
					&lt;td&gt;1.06682&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;gimp&#34;&gt;Gimp&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/gimp-bump.svg&#34; alt=&#34;Gimp bump results bar chart diagram. See the table below for a screen reader accessible alternative&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;247.506&lt;/td&gt;
					&lt;td&gt;20.2708&lt;/td&gt;
					&lt;td&gt;227.236&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;363.372&lt;/td&gt;
					&lt;td&gt;13.0572&lt;/td&gt;
					&lt;td&gt;350.315&lt;/td&gt;
					&lt;td&gt;35.586&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;300.3&lt;/td&gt;
					&lt;td&gt;10.1874&lt;/td&gt;
					&lt;td&gt;290.113&lt;/td&gt;
					&lt;td&gt;49.7434&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;emacs&#34;&gt;Emacs&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/emacs-bump.svg&#34; alt=&#34;Emacs bump results bar chart diagram. See the table below for a screen reader accessible alternative&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;110.331&lt;/td&gt;
					&lt;td&gt;39.7443&lt;/td&gt;
					&lt;td&gt;70.5862&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;177.734&lt;/td&gt;
					&lt;td&gt;44.7264&lt;/td&gt;
					&lt;td&gt;133.007&lt;/td&gt;
					&lt;td&gt;-12.5355&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;136.6&lt;/td&gt;
					&lt;td&gt;40.7356&lt;/td&gt;
					&lt;td&gt;95.8641&lt;/td&gt;
					&lt;td&gt;-2.49415&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;open-mpi&#34;&gt;Open MPI&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/openmpi-bump.svg&#34; alt=&#34;Open MPI bump results bar chart diagram. See the table below for a screen reader accessible alternative&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;139.54&lt;/td&gt;
					&lt;td&gt;3.33596&lt;/td&gt;
					&lt;td&gt;136.204&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;214.708&lt;/td&gt;
					&lt;td&gt;4.38082&lt;/td&gt;
					&lt;td&gt;210.328&lt;/td&gt;
					&lt;td&gt;-31.3209&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;167.518&lt;/td&gt;
					&lt;td&gt;3.26096&lt;/td&gt;
					&lt;td&gt;164.257&lt;/td&gt;
					&lt;td&gt;2.24831&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Once again, looking at the closure sizes, we can see we are experiencing a loss in terms of compression efficiency. We have to pay back that loss with an even greater substitution performance to be better or even on par with the NAR-based substitution.&lt;/p&gt;
&lt;p&gt;So, does a increase in substitution granularity helps when substituting a single derivation?&lt;/p&gt;
&lt;p&gt;The answer is: it depends.&lt;/p&gt;
&lt;p&gt;While it&amp;rsquo;s clear that on a NixOS closure level the gains are almost always considerable, on a single derivation substitution, the gains will greatly depend on the content of the derivation. In the case of a derivation mostly containing text files such as Gimp, we can expect lot of savings. In the case of a derivation mostly consisting of binary data (Firefox, Emacs), the gains are likely to be residual.&lt;/p&gt;
&lt;p&gt;Casync is performing consistently worse than the compressed files approach. This can probably be explained by two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Loss of the file boundaries: the chunks are generated from a serialized stream containing the whole store path content. It&amp;rsquo;d probably be a good idea to instead chunk the files individually in our use case.&lt;/li&gt;
&lt;li&gt;Shy compression: the Casync closure size is almost twice (!!) as big as the NAR one. While the small size of the chunks can explain to some extent this compression efficiency loss, &lt;code&gt;nix-casync&lt;/code&gt; is also using lzma to compress its chunks. Moving to more aggressive xz compression could be a good idea here.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;long-road-ahead&#34;&gt;Long Road Ahead&lt;/h2&gt;
&lt;p&gt;So, what&amp;rsquo;s the take-away here?&lt;/p&gt;
&lt;p&gt;Well, to me, the main one is that the current Nix/Guix substitution bloat story is not helpless. We can expect some serious improvements by increasing the substitution granularity. Be it using a file-based substitution mechanism or using a chunk-based one.&lt;/p&gt;
&lt;p&gt;It would probably be worth investing some resources into pushing these substitution mechanisms to a production-ready state. We should also seriously consider using them as the default substitution mechanism once they&amp;rsquo;re ready. And potentially give up on the NAR-based approach.&lt;/p&gt;
&lt;p&gt;Increasing the substitution granularity won&amp;rsquo;t sadly come for free in terms of complexity. It&amp;rsquo;ll affect a lot of things, possibly including rethinking the binary cache signature mechanism. We&amp;rsquo;ll likely need quite some time before being able to confidently replace the NAR-based substitution mechanism. It&amp;rsquo;s definitely not a weekend project.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Special thanks to &lt;a href=&#34;https://profpatsch.de&#34;&gt;Profpatsch&lt;/a&gt; for his spot on review.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Full disclosure, while it&amp;rsquo;s okay to omit them in the context of this article, there are two notable exceptions to this input-addressed nature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fixed Output Derivations (FODs): they are, as their name implies, already output-addressed. We know, at build time, their content hash. We use them to deterministically download stable artifacts from the Internet. EG. bootstrap seed, source code, binaries…&lt;/li&gt;
&lt;li&gt;A content-addressed store mode is available through an experimental feature. See &lt;a href=&#34;https://github.com/NixOS/rfcs/blob/master/rfcs/0062-content-addressed-paths.md&#34;&gt;RFC#62&lt;/a&gt; for more informations.&lt;/li&gt;
&lt;/ul&gt;
&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Bias Warning: the main author of nix-casync is an online friend.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    </channel>
</rss>
