コンテンツにスキップ
use-cases / a-ci-cache-thats-just-two-curl-commands / hero
PIPE · CI · COST OPTIMIZE

2 つの curl コマンドだけの CI キャッシュ

パイプラインの最初のジョブが node_modules を tar して Hoody URL にパイプします。20 個の下流ジョブが同じ URL を curl して untar します。?n=20 を使うとプロデューサーは 20 のワーカー全員が接続するのを待ち、その後 1 度ストリーム — 全員にファンアウトされます。S3 バケットも、キャッシュアクションも、転送料金もありません。

パイプ docs を読む
use-cases / a-ci-cache-thats-just-two-curl-commands / flow

全体のメカニズムを 2 つのシェル行で

クライアントライブラリも、デーモンも、SDK もありません。プロデューサーは tarball を PUT /pipe/cache?n=20 にストリームします。各ワーカーは GET /pipe/cache?n=20 からそれをストリームアウトします。パイプはバイトがフライト中の間だけ保持し — ディスクには決して書きません。

PUT · PRODUCERビルドごとに 1 回実行

キャッシュを生成

$tar c node_modules|zstd|curl -T -
https://hoody.com/pipe/cache?n=20
# 20 のレシーバーが接続するまでブロック、その後 1 度ストリーム

tar が node_modules をパックし、zstd が即座に圧縮し、curl がバイトをそのままパイプパスに PUT します。一時ファイルも、upload-artifact ステップも、バケット認証情報もありません。

GET · CONSUMERすべてのワーカーで実行

キャッシュを消費

$curlhttps://hoody.com/pipe/cache?n=20
| zstd -d | tar x
# fan-out: every worker gets an identical copy

各テストワーカーが同じ URL を GET し、解凍し、作業ディレクトリに untar します。遅いワーカーはプロデューサーにバックプレッシャーをかけますが、速いワーカーをブロックすることはありません。

コーディネーション全体?n=20

クエリパラメーター 1 つ。プロデューサーとコンシューマーが同じ n に合意します。パイプはちょうどその数のレシーバーが接続するまでアップロードを保持し、その後ゲートを開きます。

use-cases / a-ci-cache-thats-just-two-curl-commands / relief

支払いをやめるもの

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。無効化するものも、退避させるものもありません。ブランチが死ぬと、そのパスは要求されなくなり、それがライフサイクル全体です。

use-cases / a-ci-cache-thats-just-two-curl-commands / compare

コストの形、Before と After

実際のワークロード — 約 800 MB の node_modules、20 の並列テストワーカー、1 日 100 回の CI 実行 — では、ほとんどの請求はストレージではなく転送料金です。

S3 + GH ACTIONS CACHE

20× 転送量

20 のワーカーがそれぞれ S3 からキャッシュをプルします。800 MB tarball を 20 回ダウンロードすると、CI 実行ごとに 16 GB の転送量です。バケット自体は簡単な部分で — 積み重なるのは転送料金です。

VS
HOODY PIPE · ?n=20

1× 転送

プロデューサーが 800 MB を 1 度ストリームします。パイプはフライト中のバイトを 20 のレシーバー全員にファンアウトします。1 回の転送、レシーバー単位の倍数なし、ストレージ料金なし。

数字は典型的な Node モノレポキャッシュの説明用です。実際の節約は tarball サイズ、ワーカーのファンアウト、プロバイダーがキャッシュリージョンから請求する転送料金によります。形 — ワーカー数に対して線形 vs 一定 — は不変です。

use-cases / a-ci-cache-thats-just-two-curl-commands / punchline

キャッシュレイヤーは HTTP です。ずっとそうでした。私たちが気づいていなかっただけです。

キャッシュはストレージのためのものではありませんでした。再構築せずに同じバイトを N 個のワーカーに届けることが目的でした。HTTP は既にそれをします — 1 つの URL を既知の数のレシーバーにファンアウトさせれば。バケットは、私たちが持っていなかったファンアウトのための回避策でした。

BYTES

1 度ストリーム、ファンアウト

1 PUT、n GET、同一バイト。バックプレッシャーはレシーバー単位なので、遅いワーカーが速いワーカーを遅くしません。

PUT /pipe/cache?n=20
TIME

フライト中のみ生存

パイプは転送中だけバイトを保持し、終わると忘れます。退避させるもの、ライフサイクル管理するもの、バックアップするものはありません。

TTL ≤ 5 分 · その後退避
PATH

ブランチがパス

各ブランチが独自のパスを選びます。共有キースペースなし、衝突なし。パスがキャッシュキーであり同時に URL です。

/pipe/cache/[branch]
パイプ API を読む
use-cases / a-ci-cache-thats-just-two-curl-commands / replaces

これが置き換えるもの

ほとんどの CI キャッシュは同じ問題を解きます: 同じ tarball を N 個のワーカーに届ける。それらはストレージと転送量経由で行います。パイプはワイヤー経由で行います。

  • AWS S3 (キャッシュバケット + 転送料金)明日上書きするストレージ + プルごとの転送料金
  • GitHub Actions cacheYaml キー、ランナー固有の癖、10 GB の上限
  • BuildJet / Garnix CI cacheより速いが、依然ベンダー単位、依然バイト単位
  • Bazel リモートキャッシュBazel に全振りなら最高、そうでなければ重い
  • Turborepo リモートキャッシュVercel ホスト、モノレポ向け、独自の意見
  • Earthly satellite cache別のデーモン、別のバケット、別の請求
  • カスタム rsync キャッシュNFS ボックスと、誰もがローテートを忘れる SSH キー
use-cases / a-ci-cache-thats-just-two-curl-commands / cta

2 つの curl コマンド。1 つの URL。20 のワーカーが供給される。

パイプ docs を読む
use-cases / a-ci-cache-thats-just-two-curl-commands / related

他のユースケースを読む