Nixのsubstituteは「バイナリパッケージ」よりbuild resultに近い
Nixを「ビルド済みパッケージマネージャー」と呼ぶことは利用者視点では間違いではないが、内部モデルを理解するには少し不正確である。Nixのbinary cacheから取得されるものは、独立した別形式のbinary packageというより、本来buildして得るNix store objectの代替物(substitute) と考える方がよい。
ビルド済みパッケージ配布を一括りにしない で分類した build result substitution 型の代表例である。
NixのHTTP Binary Cache Storeはsubstituterとして利用でき、store pathに対応するNARとmetadataを配布する。クライアントから見ると、あるderivationのoutputがlocal storeに存在しないとき、substituterに同じstore pathが存在すればそれを取得できる。存在しなければ通常のbuild経路に戻れる。
この構造の重要な点は、package definitionとbinary distributionが原理上分離していることである。nixpkgs等が「どう作るか」を記述し、binary cacheはそのbuild resultを再利用する。そのため同じNix expressionを使っていても、ある環境ではcache hitでdownloadだけが起こり、別の環境では実際のbuildが起こり得る。
GNU Guixはこの概念をより直接的に説明しており、substituteを「local build resultの代替」と呼ぶ。さらに、substituteはpackage binaryに限定されず、derivation buildの結果であればsource tarballであってもよいとしている。この説明はNix/Guix系において「binary cache」という名称だけを見ると本質を取り違えやすいことを示している。
一方で、cacheから得たobjectをそのまま信用してよいとは限らない。通常のNix store pathの多くはcontentそのものからpathが決まるわけではないため、input-addressed objectを別storeから受け入れる際には署名によるtrustが重要になる。これは Nixのstore pathは通常content-addressedではない と binary cacheの公開鍵を追加することはbuilderを信頼することに近い の問題である。
このモデルには実務上の利点がある。binary cacheは「唯一の配布形式」ではないため、cache availabilityとbuildabilityを分離できる。private cache、CI cache、remote builderを追加してもpackage definition自体を変える必要がない。一方で、同じderivationが本当にbit-for-bit同じoutputを作るかは別問題であり、substitution model自体がreproducible buildを自動的に保証するわけではない。この点は lockfileで依存を固定してもreproducible buildにはならない に分けて考えるべきである。
したがってNixのbinary cacheは、aptやCondaのように「repositoryにあるbinary packageを解決して入れる」というより、build graphで定義されたstore objectを、buildせずに別storeから代入する仕組み と捉えるのが近い。