コンテンツにスキップ
use-cases / push-one-build-to-thirty-ci-workers / hero
PIPE · CI ファンアウト

1 つのビルドを 30 の CI ワーカーへ同時にプッシュ

マトリックス CI が 30 のテストランナーにファンアウト。それぞれが同じ 800 MB のイメージを必要とする。tarball を ?n=30 で 1 つのパイプパスにストリーミング。30 のワーカーすべてが同じ URL を curl。バイトは一度通り、サーバーは何も保持せず、レジストリの認証情報はローテーションされない。

パイプのドキュメントを読む
use-cases / push-one-build-to-thirty-ci-workers / mechanism

30 の curl が 1 つのストリームになる仕組み

パイプはディスクのないファンアウトルーター。送信側の /api/v1/pipe/[path]?n=30 への POST は、同じ URL に同じ n で 30 の受信側が接続するまでブロック。その後、バイトはビルドコンテナからすべてのランナーへそのまま、最も遅い受信側の速度で同時に流れる。

3 ステップ。レジストリなし、S3 なし、キャッシュアクションなし。PIPE · ?n=30
01

ビルドが一度ストリーミング

tar c | curl -T - https://pipe/.../build?n=30

ビルドコンテナが tarball をそのまま curl にパイプ。ファイル書き込みなし、レジストリプッシュなし。

02

パイプがフリートを待つ

POST /api/v1/pipe/[path]?n=30

サーバーは 30 すべての受信側が接続するまで送信側を保持。n の不一致は 400 を返す。事前接続済みの受信側でも問題ない。

03

30 すべてが同じ URL をプル

curl https://pipe/.../build?n=30 | tar x

各ランナーが同一のバイトを取得。バックプレッシャーは送信側の帯域幅ではなく、最も遅い受信側から流れる。

何も永続化しない。何もキャッシュされない。パイプは接続を仲介し、その後身を引く。最も遅いランナーが終わると、転送は終わる — そして URL は消える。

use-cases / push-one-build-to-thirty-ci-workers / numbers

マトリックスにかかるコスト

ナイーブ: 同じ 800 MB の tarball を 30 回レジストリプル、30 のコールドキャッシュ、30 のネットワークラウンドトリップ。パイプ: 1 エグレス、1 転送、最も遅い受信側がペースを設定。

壁時計時間

12 秒

ラインレートで 1 つのエグレス。最も遅い受信側がペースを設定するが、誰も再ダウンロードしない。

エグレス

1× / ビルド

バイトはビルダーから一度だけ出る、パイプでファンアウト。S3 GET 料金なし、Docker Hub プルなし。

ストレージ

0 バイト

パイプはディスクに何も保持しない。クリーンアップするレジストリなし、無効化するキャッシュキーなし。

壁時計時間の数値は、ビルドコンテナと同じ地域ネットワーク上の 30 ウェイマトリックスを想定; 地域間転送はパイプではなく、地域間帯域幅でゲートされる。

use-cases / push-one-build-to-thirty-ci-workers / powers

ファンアウトが解放するもの

ビルドが 1 つの URL と 30 の curl になると、CI 足場のスタックが消える。エイジアウトするアーティファクトストレージなし。ローテーションするレジストリ認証情報なし。デバッグするキャッシュアクションなし。

最も遅い受信側がペースを設定

バックプレッシャーはパイプに組み込まれている。速いワーカーは遅いワーカーを待ってレジストリラウンドトリップを無駄にしない — パイプで待ち、その後同じレートで吸収する。誰も再ダウンロードしない。

ローテーションするレジストリ認証情報なし

レジストリにプッシュされるものがないので、何かを認証する必要がない。URL 自体が認証情報 — 短命で、1 つの転送にスコープされ、ビルドが終わると追い出される。

S3 の請求書なし、エグレスのサプライズなし

バイトはビルダーから一度だけ出る。パイプがブロードキャスト。マトリックス実行ごとに 30 のレジストリプルではなく、ビルドごとに 1 エグレスを支払う。

無効化するキャッシュキーなし

パイプはキーごとではなく、ビルドごと。ミスヒットする GitHub Actions キャッシュなし、buildx レイヤーの謎なし、先週の main からの古い tarball なし。

イメージだけでなく、あらゆる tarball で動作

同じパターンが node_modules、.pnpm-store、target/、wheel キャッシュ、データセットシャードを処理。ストリーミングできるなら、ファンアウトできる。

use-cases / push-one-build-to-thirty-ci-workers / punchline

1 送信側。30 受信側。S3 請求書ゼロ。

90 秒と S3 ヒットを要した 30 ウェイのプッシュが、12 秒と 1 つのエグレスで完了。誰も再ダウンロードしない。レジストリ認証情報はローテーションされない。マトリックスが完了すると URL は自動退避。

  • 1 エグレス
  • 30 受信側
  • 0 ストレージ
  • 0 認証情報
パイプ API を開く
use-cases / push-one-build-to-thirty-ci-workers / replaces

これが置き換えるもの

マトリックス CI フローが通常組み立てるピース — レジストリ、キャッシュアクション、ミラー、カスタムアップロードステップ。パイプはそれらを 1 つの URL に折りたたむ。

  • AWS S3 (レジストリストレージ + エグレス)ビルドあたり 30× の GET 料金、エビクションポリシー、IAM ロール
  • GitHub Actions キャッシュ10 GB キャップ、キーの衝突、ブランチごとのスコープ
  • Docker Hub プルレート制限あり、スロットリング回避のために有料ミラー
  • npm / pnpm レジストリミラー公開レジストリをスキップするためだけのセルフホスト Verdaccio
  • カスタム CI キャッシュアクションBash の糊、S3 SDK、有効期限ロジック、壊れたときのオンコール
  • Buildx レイヤーキャッシュエクスポートレイヤー形式の癖、ランナー間のキャッシュミス
use-cases / push-one-build-to-thirty-ci-workers / cta

同じ tarball を 30 回プッシュするのを止めよう。一度プッシュ。30 の curl にストリームを共有させよう。

パイプ API を読む
use-cases / push-one-build-to-thirty-ci-workers / related

他のユースケースを読む