prebuilt binaryはbuild matrixが増えるほどcache効率が落ちる
prebuilt binaryを増やせばsource buildを減らせるが、binary compatibility条件を細かくするほど必要なartifact数も増える。
linux/amd64だけではビルド済みバイナリの互換性を表せない で扱ったように、実際の互換性条件はOS/architectureだけとは限らない。glibc、compiler、CPU target、CUDA、dependency version、optional featureなどを区別すると、理論上のbuild matrixはそれらの直積になる。
これはprebuilt distributionの基本的なtrade-offである。
compatibility dimensionを粗くするとartifact数は少なくなるが、「downloadできたが実行できない」「古いglibcで動かない」といった問題をupstreamの暗黙ルールへ押し出す。逆にdimensionを細かくmetadata化すると適合性は上がるが、build・storage・index・cache missのcostが増える。
Spackはこの問題が特に見えやすい。package versionだけでなくcompiler、variant、dependency graph等を含むconcretized specに対してBuild Cacheを作れるため、HPC環境の細かな構成差をbinaryとして表現できる。その一方、Spackの公式documentationはbuild cacheがbinary追加とともに巨大化し、検索効率が落ちる問題を明記している。
対策としてapplication/workflowごとにcacheを複数のstackへ分割できるが、今度はcommon dependencyがstackごとに重複し、storageとbuild computationが増える。またstackを狭くすると目的のbinaryが含まれずdirect cache hitが減る可能性がある。つまりindex size・deduplication・hit rateの三者にtrade-offがある。
upstream release型でも同じ問題は形を変えて存在する。例えばGitHub ReleasesでOS × architecture × libcをすべてartifact名に展開すれば、release asset数とCI job数が増える。そのため多くのprojectはlinux-amd64程度の粗いmatrixに留め、glibc baseline等はdocumentation上の暗黙条件にする。
Nixのbinary cacheは依存closureをstore object単位で共有できるため、同じdependency store pathを複数packageから再利用できる。しかしbuild inputが変われば別store pathになるため、compilerやbuild optionの組み合わせを増やせばやはりcache object数は増える。
ここでOCI/CAS的なstorageとの相性も問題になる。artifactをcontent-addressed chunk/layerへ分割すれば同一contentの重複排除はしやすいが、「どのcompatibility specにどのclosureを対応させるか」というindex問題そのものは消えない。
prebuilt package systemを大規模運用するときは、単に「何platformをbuildするか」ではなく次を設計する必要がある。
- compatibility dimensionをどこまでartifact identityへ入れるか
- common dependencyをどの粒度でdeduplicateするか
- cache indexをどうpartitionするか
- cache miss時にsource buildへfallbackするか
- 古いmatrixをいつgarbage collectするか
これは「全組み合わせをビルドすれば最も互換性が高い」という単純な問題ではない。binary cacheはbuild時間を利用者からprovider側へ移す仕組みでもあり、matrixが増えるほどprovider側の継続的な計算・保存コストが支配的になる。