
1 つのサーバーで 60 のコンテナ
1 つのベアメタルボックスで数十から数百の Hoody コンテナを実行。KSM と BTRFS のデデュプでマージナルコストはほぼゼロ。
パイプラインの最初のジョブが node_modules を tar して Hoody URL にパイプします。20 個の下流ジョブが同じ URL を curl して untar します。?n=20 を使うとプロデューサーは 20 のワーカー全員が接続するのを待ち、その後 1 度ストリーム — 全員にファンアウトされます。S3 バケットも、キャッシュアクションも、転送料金もありません。
クライアントライブラリも、デーモンも、SDK もありません。プロデューサーは tarball を PUT /pipe/cache?n=20 にストリームします。各ワーカーは GET /pipe/cache?n=20 からそれをストリームアウトします。パイプはバイトがフライト中の間だけ保持し — ディスクには決して書きません。
tar が node_modules をパックし、zstd が即座に圧縮し、curl がバイトをそのままパイプパスに PUT します。一時ファイルも、upload-artifact ステップも、バケット認証情報もありません。
各テストワーカーが同じ URL を GET し、解凍し、作業ディレクトリに untar します。遅いワーカーはプロデューサーにバックプレッシャーをかけますが、速いワーカーをブロックすることはありません。
?n=20クエリパラメーター 1 つ。プロデューサーとコンシューマーが同じ n に合意します。パイプはちょうどその数のレシーバーが接続するまでアップロードを保持し、その後ゲートを開きます。
CI キャッシングはかつて税金でした: 明日上書きするストレージ、ワーカーがプルするたびの転送料、キャッシュアクションの癖に費やすエンジニアリング時間。パイプは 3 つの項目すべてを一度に削除します。
ストレージがないため、S3 バケットはありません。パイプは転送が終わった瞬間にバイトを忘れるため、GB 月単位や GB 出力単位で課金するものはありません。キャッシュは項目ではなくなります。
yaml エンコードされたキーも、save-cache / restore-cache の分割も、なぜ正しいランナーでキャッシュヒットが発生しなかったかのデバッグもありません。ただ curl だけ。同じ 2 行が GitHub Actions、BuildKite、Jenkins、ノートパソコン、cron コンテナで動きます。
ブランチは異なるパスを使うだけです。/pipe/cache/main、/pipe/cache/feat-x、/pipe/cache/PR-742。無効化するものも、退避させるものもありません。ブランチが死ぬと、そのパスは要求されなくなり、それがライフサイクル全体です。
実際のワークロード — 約 800 MB の node_modules、20 の並列テストワーカー、1 日 100 回の CI 実行 — では、ほとんどの請求はストレージではなく転送料金です。
20× 転送量
20 のワーカーがそれぞれ S3 からキャッシュをプルします。800 MB tarball を 20 回ダウンロードすると、CI 実行ごとに 16 GB の転送量です。バケット自体は簡単な部分で — 積み重なるのは転送料金です。
1× 転送
プロデューサーが 800 MB を 1 度ストリームします。パイプはフライト中のバイトを 20 のレシーバー全員にファンアウトします。1 回の転送、レシーバー単位の倍数なし、ストレージ料金なし。
数字は典型的な Node モノレポキャッシュの説明用です。実際の節約は tarball サイズ、ワーカーのファンアウト、プロバイダーがキャッシュリージョンから請求する転送料金によります。形 — ワーカー数に対して線形 vs 一定 — は不変です。
キャッシュレイヤーは HTTP です。ずっとそうでした。私たちが気づいていなかっただけです。
キャッシュはストレージのためのものではありませんでした。再構築せずに同じバイトを N 個のワーカーに届けることが目的でした。HTTP は既にそれをします — 1 つの URL を既知の数のレシーバーにファンアウトさせれば。バケットは、私たちが持っていなかったファンアウトのための回避策でした。
1 PUT、n GET、同一バイト。バックプレッシャーはレシーバー単位なので、遅いワーカーが速いワーカーを遅くしません。
PUT /pipe/cache?n=20パイプは転送中だけバイトを保持し、終わると忘れます。退避させるもの、ライフサイクル管理するもの、バックアップするものはありません。
TTL ≤ 5 分 · その後退避各ブランチが独自のパスを選びます。共有キースペースなし、衝突なし。パスがキャッシュキーであり同時に URL です。
/pipe/cache/[branch]ほとんどの CI キャッシュは同じ問題を解きます: 同じ tarball を N 個のワーカーに届ける。それらはストレージと転送量経由で行います。パイプはワイヤー経由で行います。
2 つの curl コマンド。1 つの URL。20 のワーカーが供給される。