zudo-test-wisdom
GitHub リポジトリ

検索したい単語を入力

いつでも検索バーを開ける

CIランナーのサイジング

リポジトリの可視性で決めるプロバイダー選択、Blacksmithランナーの形状と料金、最小構成を既定とするスタンス、そして「大きい/別のランナーが本当に効くのはいつか」を判断する4つのルール。

このページが埋める空白

このガイドの他のページは、「CIランナー」を単一の固定された存在であるかのように扱っています。実行ティアはPRゲートが「10分未満」で走ることを前提とし、重いテストの判断ルールは環境的に実行不能なテストを「対応可能なハードウェア」へ送り、定期再試験はプラットフォーム依存のスペックを「ホスト型のmacOS」に載せます。これらのページはどれも、2 vCPUのLinuxランナーが存在し、安価で、既定で手を伸ばす対象である、ということを暗黙のうちに前提にしています。

しかしそのどれもが、その下の層を文書化していません。どんなランナーの形状が存在し、どう課金され、そして「大きい/別のランナーが本当に効くのはいつか」を判断するルールは何か。 その知識はたいてい、あるプロジェクトのワークフローファイル冒頭のコメントに書かれたまま、そこで死んでいきます — 次のプロジェクトが見つけられる場所には決して書き残されないのです。このページはその欠けた層であり、これらの戦略がチューニング対象としているBlacksmithというランナープロバイダーに対して書かれています。

Note

このページの料金は、2026-07-05にBlacksmithのライブページ(ランナー概要、料金)に対して再検証しました。 従量課金のレートは変動します。すべての金額を「2026-07時点」のものとして扱い、予算に記載する前に必ず2つの出典ページを再確認してください。

サイジングの前に、プロバイダー選択

以下のすべてのルールは、CIの分数に金を払っていることを前提にしています。その前提がそもそも成り立つかどうかは、どのラベルやサイジング表よりも前に来るひとつの問いで決まります。リポジトリはパブリックか、プライベートか?

  • パブリックリポジトリ → 標準のGitHubホスト型ランナー一択。 最大の理由は、それだけで十分な理由です:無料だからです。GitHubの課金ポリシーは直接こう述べています — 「GitHub Actions usage is free for self-hosted runners and for public repositories that use standard GitHub-hosted runners(セルフホスト型ランナーと、標準のGitHubホスト型ランナーを使うパブリックリポジトリでは、GitHub Actionsの利用は無料)」(2026-07-05検証)。どんな分単価の割引も、請求額ゼロには勝てません。パブリックリポジトリなら、プロバイダーの問いはここで終わりです。注意点はひとつだけで、それはこのページの既定スタンスの予告でもあります:より大きなランナーは、パブリックリポジトリであっても常に課金されます — パブリックリポジトリのジョブが標準形状を離れた瞬間に、請求額が存在し始めるのです。

  • プライベートリポジトリ → Blacksmith。理由の第1位はコストです。 プライベートリポジトリはプランに込みの分数を消費し、使い切った後は1分ごとにGitHubの定価を払います。エージェント駆動のPR量では込みの枠は数日で蒸発するため、効いてくるのは限界的な1分の値段です — そして、より速いマシンでおよそ半額というBlacksmithのLinux分単価が、その比較に完勝します。このガイドのプライベートリポジトリのCIがBlacksmithで走っているのはそれが理由であり、このページの残りがBlacksmithに対して書かれているのも同じ理由です。

可視性が先、プロバイダーが次、サイジングは最後。以下のセクションが意味を持つのは、この問いがあなたを従量課金のランナーの上に置いたときだけです。

Blacksmithを1分で

Blacksmithのランナーは、GitHubホスト型ランナーのドロップイン置き換えです。runs-on を変えるだけで、他は何も変わりません。価値の要点は、より高速なマシンをより低い分単価で、GitHubの「込みの分数」モデルではなく従量課金で使える、という点にあります。

ランナーのラベル

ラベルは形状を直接エンコードしているので、ジョブのサイジングはトークン1つ分の編集で済みます。

jobs:
  test:
    runs-on: blacksmith-4vcpu-ubuntu-2204
軸値
vCPU2vcpu、4vcpu、8vcpu、16vcpu、32vcpu
OSイメージubuntu-2204(安全な既定値)、ubuntu-2404
アーキテクチャx64(既定)、arm64には -arm サフィックス

つまり blacksmith-8vcpu-ubuntu-2404-arm は、Ubuntu 24.04上の8 vCPU arm64ランナーです。既定のイメージには ubuntu-2204 を選ぶのが無難です — GitHubの長年の ubuntu-latest の挙動に最も近いため、ワークフローを移行してもツールチェーンのバージョン差でつまずくことがめったにありません。ubuntu-2404 へは、ジョブが実際に新しいベースを必要とするときに、意図的に移りましょう。

料金と無料枠

  • 月3,000分の無料枠。x64の2 vCPU相当分で計上されます。より大きい形状、arm、Windows、macOSの形状は、同じ予算をそれぞれの倍率で消費します(arm 2 vCPU分はx64より安く、Windows分は高く、macOS分ははるかに高い — 後述)。

  • **x64 2 vCPUランナーで約 0.004/分** — 標準的な2コアLinuxランナーに対するGitHubホスト型の定価 0.008/分の、およそ半分です。Blacksmith自身の料金ページは、分単価と速度を組み合わせた優位性を「総コストでおよそ3分の2の削減」と表現しています。マシンが同じジョブをより速く走らせるためです。

  • 料金はvCPU数にほぼ線形にスケールします。 料金ページは2 vCPUのベースについて正確な数値を公開しており、そこから4 vCPUランナーは2 vCPUレートのおよそ2倍(約 0.008/分)、8 vCPUはおよそ4倍(約 0.016/分)……と課金されます。この線形性が、後述のルール1を支える要の事実です — 覚えておいてください。

透過的な依存キャッシュ — そして、それを省くべきとき

Blacksmithは、標準的なキャッシュアクション(actions/cache や普及しているエコシステムのラッパー)が高スループットで透過的に利用するキャッシュをコロケーション(同一拠点配置)しています。恩恵を受けるのに特別な設定は要りません。

直感に反するのは、明示的なキャッシュをまったく追加しないほうがよいことが多い、という点です。

Warning

Blacksmithのインストール速度では、actions/cache の保存/復元ステップを追加することは、しばしば差し引きで損になります。 x64 2 vCPUランナー上で、pnpm install --frozen-lockfile はワークフローにキャッシュステップを一切置かずに約2秒で完了することが観測されています。保存/復元の往復 — キーのハッシュ化、tarballのアップロード、tarballのダウンロード、展開 — は、それが省こうとしていたインストールよりも、実測時間でもYAMLの記述量でも、日常的に高くつきます。

これは、より遅いランナーから持ち込まれる、あらゆるCIエンジニアが身につけている反射 — 「依存はキャッシュせよ」という無条件の助言 — に反します。ここではそれは条件付きです。まずキャッシュなしのインストールを計測してください。 すでに2〜3秒なら、キャッシュは純粋なオーバーヘッドです。明示的なキャッシュは、本当に高コストなアーティファクト — コンパイル済みのRustのターゲットディレクトリ、Playwrightのブラウザダウンロード — にだけ手を伸ばし、高速なパッケージインストールには使わないでください。

macOS(Apple Silicon)ランナー

macOSは別次元のコストであり、その違いの形が、あなたがそれをどう使ってよいかを決めます。

  • 最小のmacOS形状は6 vCPUです。 小さくて安価なMacランナーは存在しません — 入口の価格が6 vCPUマシン(blacksmith-6vcpu-macos-latest や、blacksmith-6vcpu-macos-15 のようなピン留めバリアント)であり、その上に12 vCPUの選択肢があります。

  • 課金はx64 2 vCPU分のおよそ20倍です。 6 vCPUのmacOS分は約 **0.08/分** — x64の約 0.004/分レートの20倍です。設計で織り込むべきなのは、絶対額ではなくこの倍率です。

その帰結は、好みではなく、動かしがたいアーキテクチャ上のルールです。

Danger

Macレーンは schedule: + workflow_dispatch: のジョブとしてなら成立します — エージェント駆動のPR量で、PRごとに走らせることは決してありません。 20倍の倍率は、すべてのプルリクエストで走らせた瞬間に、ありふれたMacジョブを4桁ドルの月次コスト項目に変えてしまいます。

具体例:夜間の45分Mac試験は 45 × $0.08 ≈ $3.6/晩 ≈ 約 $110/月 のコストです — 上限があり予測可能です。同じジョブを、月約400回のCI実行でPRごとに走らせるよう配線すると、400 × 45 × $0.08 ≈ 約 $1,400/月 になります。同じ作業が、トリガーだけの違いで一桁分かけ離れるのです。

その計算には、隠れた好機があります。予測可能な約 $110/月 という数字こそ、Metal/WebGLのカバレッジが現在は開発者のローカルMacでしか走らないプロジェクトにとって、スケジュール型の実GPU重量レーンを現実的にするものです。これはまさに、重いテストの判断ルールの「環境的に実行不能」「プラットフォーム的に実行不能」のケースです。実GPUや実macOSを必要とするテストは、対応可能なハードウェア上のスケジュール型T3ティアへ移ります。ホスト型のApple Siliconランナーは、そのティアを「誰かがリリース前にローカルで走らせてくれることを祈る」ではなく、上限のある月次コストにするものです。スケジュール型のmacOSジョブそのものの配線については、定期再試験 & 夜間試験を参照してください。

ランナーサイジングの4つのルール

ラベルと料金は簡単な部分です。難しいのは、大きい/別のランナーが正しい道具となるのはいつかを見極めることです — なぜなら「とにかくコアを増やせばいい」という直感は、およそ半分は正しく、残り半分は積極的に間違っているからです。この4つのルールが、このページの核心となる知恵です。

既定値:動作する最小の形状

4つのルールが働き始める前に、まず既定値を守ってください。すべてのレーンは、確実に完走する最小・最安の形状で走らせる — 以下のどれかが理由を名指しするまでは 2vcpu です。

その根拠は、CIのレイテンシが実際に誰の時間を消費しているのかという、AI開発時代の観察です。実装がエージェント駆動なら、遅いレーンの実測時間はエージェントのターンとターンの合間に落ち、人間が画面の前で待つ時間には落ちません — エージェントは人間がレビューするより速く成果を生み出すので、PRゲートが数分余計にかかっても、人間のレビューの歩調の内側に余裕で収まります。誰も待っていないレイテンシに、金を払う価値はありません。

だから、大きい・特殊なランナーには、雰囲気ではなく明示的に名指しされた理由が必要です。正当な理由の例:チームが本当に座って待つプレビューデプロイ、OS固有のビルド環境(macOS、Windows)、ルール2・ルール3で診断された失敗モード。ルール1が教えてくれるのは、大きなランナーを買うと決めたときに何が買えるか、です — 既定は依然として、そのレイテンシに名前の付いた持ち主が現れるまで買わないことです。

ルール1:大きなランナーはコスト削減ではなくレイテンシのレバー

並列化可能なワークロードでは、大きくすることは速度を買うのであって、節約を買うのではありません。

Playwrightのワーカーは既定ではコアにスケールし、そして — 上の料金セクションのとおり — 料金もコアに線形にスケールします。この2つの線形の関係は相殺します。4から8 vCPUへ倍にすれば、実測時間はおよそ半分になる一方、総コストはほぼ据え置きです。分単価は2倍を払いますが、分数がおよそ半分になるからです。この相殺は完全に最初の脚に乗っています — 成り立つのは、ジョブの実効ワーカー数が追加されたコアに実際にスケールしている間だけです。

4vcpu:  ~10 min  ×  ~$0.008/min  ≈  $0.08
8vcpu:  ~5 min   ×  ~$0.016/min  ≈  $0.08   ← half the wait, same bill

したがって、この判断は純粋にフィードバックループの速度についてのものです。より速いPRゲートがエンジニアリングの注意を割く価値があるなら、並列化可能なジョブは大きくしましょう。待ち時間がすでに許容できるなら、小さいままにしておきます。請求額が縮むと期待して大きくしてはいけません — この種の作業では縮みません。

とりわけPlaywrightのスイートでは、コアを買う前に確認すべきことが1つあります。fullyParallel の既定は false で、各ファイル内のテストは単一のワーカーへ直列化されます。したがってスイートの下限は total ÷ workers ではなく最も遅い1ファイルであり、その下限を越えて追加したコアは何も買えません。fullyParallel の既定は false — メガファイルが作るテールを参照してください。

Tip

この相殺が崩れる形は2つあり、直列に見えるのはそのうち1つだけです。本質的に直列な作業 — シングルスレッドのビルドステップ、直列のマイグレーション — は、vCPUを増やしても実測時間の恩恵をまったく得られず、そのジョブにとって大きなランナーは純粋な追加コストです。そして、本質的には並列な作業も、人為的に上限をかけられていることがあります。playwright.config.ts の明示的な workers: N(あるいはCLIの固定数値の --workers=N — --workers=50% のようなパーセント指定はコアにスケールし続けます)は並行数をピン留めし、相殺の「ワーカーはコアにスケールする」側の脚を取り除きます。上限をかけられたジョブは直列ではありません — 大きなランナーは、ピン留めされたワーカー数におけるCPU飢餓をなお緩和できます。それはルール3の領分であり、ルール3の天井を持ちます — しかし、このルールが約束する自動的なスケーリングは失われます。競合が消えた点を過ぎれば、コストは上がり続け、実測時間は動かなくなります。「Playwrightだから」は、そのジョブがスケールする証明にはなりません。サイズを上げる前に、設定に workers のピン留めがないか確認してください。

ルール2:大きなランナーはメモリ律速の失敗を修正しない

これは最も高くつく間違いです。なぜなら、大きなマシンは効きそうに見え、しかもしばらくは効いているように見えることがあるからです。

JavaScriptヒープのOOMに当たっているvitest/jsdomレーンは、2から4 vCPUへ移しても修正されませんでした。大きなマシンはヒープの天井を上げますが、それは同じOOMを遅らせるだけです — プロセスは依然として新しい天井まで登りつめて死にます。失敗はメモリの蓄積に律速されており、天井が高くなっても登りの傾きは変わりません。

実際の修正は、シャード間でメモリが回収されるよう、プロセスあたりのファイル数を制限することです。

vitest --shard=1/4

シャーディングは、1つのワーカープロセスが新しいプロセスへロールオーバーするまでに読み込むテストファイルの数に上限をかけ、マシンの総RAMに関わらずピークヒープを天井以下に保ちます。注意として、--shard=1/4 は4つのシャードの最初の1つだけを実行します — CIレーンは4つすべて(1/4 から 4/4 まで、マトリクスか逐次実行で)を走らせなければならず、さもないとスイートの4分の3が黙って未実行になります。このフラグはプロセスあたりのメモリに上限をかけるだけで、それ自体でスイート全体を実行するわけではありません。(証拠:zudolab/zudo-pattern-gen#1912 — pgen(Takazudo の非公開の、Cloudflare Workers バックエンドを持つ GPU パターンジェネレーター)で、2→4 vCPUのアップグレードを生き延び、--shard がプロセスあたりのファイル数を制限してはじめてグリーンになったヒープOOMレーン。)周辺のワーカー・タイムアウト予算の設定については、Vitestパターンを参照してください。

ローカル側の対応物 -- 並行するヘビーレーンに枯渇させられる共有の開発マシンで、レバーがレーンの形ではなくアドミッションであるケース -- はローカルヘビーレーンのアドミッション制御です。

Warning

「大きくしたら通った」は、サイズが原因だった証拠にはなりません。 大きなランナーで解消したメモリ律速の失敗は、その日のファイル順序に対するノイズの閾値を下回っただけ、ということがよくあります — スイートが次に成長したときに再浮上します。修正をvCPUに帰する前に、メカニズム(ピークヒープが律速の制約なのか?)を確認してください。

ルール3:サイズを変える前に失敗モードを診断する

ルール2は、大きなランナーがメモリ問題を修正しないと言います — しかしそれは、外から見ればほとんど同じに見える別の問題は、間違いなく修正するのです。

CPU飢餓(starvation): 2 vCPUランナー上では、e2eワーカーとdevサーバーが同じ2コアを奪い合います。競合下では、スペックがタイミング予算を外し、タイムアウトとして失敗します — これは表面上、flakyな、あるいはリソース不足のテストとまったく同じように見えます。ここでは、4 vCPUへ移すことが修正になります。devサーバーとテストワーカーにそれぞれ専用のコアを与えれば、競合が消えるからです。(証拠:zudolab/zudo-pattern-gen#3321 — 2コア競合下でのスペックタイムアウトが、4 vCPUで解消。)リサイズにあわせてワーカー数を環境変数でチューニングするなら、値は境界で検証してください — NaN に変換されるPlaywrightの workers オーバーライドはゼロ件のテストを実行してgreenで終了します。出発点だったタイムアウトより、偽のgreenの方がたちが悪いのです。

この修正には、ルール1の相殺には無い天井があります。メカニズムは固定されたワーカー数における競合の緩和であって — 並列度の追加ではありません — そしてそれは飽和します。おおよそ、ピン留めされたワーカーとdevサーバーがそれぞれ自分のコアを持った時点で、それ以上のvCPUは何も買いません。workers: 2 がピン留めされた状態で4→8 vCPUへ引き上げると、2つのワーカーと1つのdevサーバーに使い切れない暇なコアを渡すだけです — 実測時間はほぼ据え置き、請求額は2倍。これは、ルール1のピン留めワーカーの罠に反対側から入った姿です。コア数は公式ではなくヒューリスティックとして扱ってください(ブラウザはサブプロセスを生み、OSも取り分を持っていきます)。本当の停止条件は、競合によるタイムアウトが消え、もう1段のサイズアップが実測時間を動かさなくなることです。その点までサイズを上げ、そこで止めてください。

つまり、どちらも「負荷で死ぬテスト」として現れる2つの失敗が、正反対の対処法を持つのです。

flowchart TD A[負荷でテストが失敗] --> B{律速している制約は何か?} B -->|ピークヒープが天井まで登り<br/>そしてOOM| C[メモリ律速<br/>ルール2] B -->|ワーカー+devサーバーが<br/>コアを奪い合う → タイムアウト| D[CPU飢餓<br/>ルール3] B -->|CPUが暇なままネットワークウォーターフォールで<br/>同一オリジンのリクエストがキューに滞留| E[コネクション飢餓<br/>下のNote参照] C --> C1[大きなランナーでは修正されない<br/>修正:vitest --shard] D --> D1[大きなランナーで修正される<br/>2vcpu → 4vcpu] E --> E1[大きなランナーでは修正されない<br/>修正:アプリ/配信レイヤー]

そこから導かれるルール:サイズを変える前に律速の制約を診断し、各ジョブをそれ自身の失敗モードに合わせてサイジングする。 ワークフロー全体を一律にサイジングしてはいけません — 同じワークフロー内のメモリ律速のvitestレーンとCPU飢餓のe2eレーンは、それぞれ異なる扱いを望んでおり、「すべて8vcpuに」という一括対応は、メモリのバグを覆い隠しつつ、それを必要としなかったジョブに払い過ぎることになります。

Note

まったく同じ症状を見せながら、どちらのレバーにも応えない第3の失敗モードがあります:コネクション飢餓 — 開発サーバーのモジュールストームがブラウザのホストごとコネクションプール(HTTP/1.1のケースでホストあたり6本)を使い果たし、CPUが暇なまま待機がタイムアウトするケースです。CPUが暇というだけでこのブランチに進んではいけません(リモートI/O、ブロックされたロック、ハングした依存も同じに見えます) — ネットワークウォーターフォールの証拠、すなわち同一オリジンのリクエストがキューで停滞している様子を必須としてください。プールはvCPUを増やしても増えません。修正はアプリ/配信レイヤーにあります。フレイク根本原因カタログのエントリ6を参照してください。

ルール4:コスト管理はランナー選びではなく、トリガー設計から生まれる

CIの請求額に対する最大のレバーは、どのランナーを選ぶかではありません — 高コストなレーンをそもそもいつ走らせるかです。

8 vCPUランナー上の重量レーンをすべてのトピックPRで発火させれば、同じレーンをスケジュールで走らせるよりもはるかに高くつき、その差の前ではランナーのサイズは端数の誤差です。上のmacOSの試算はその極端なケース — 分あたり20倍が、トリガーをすべての物語にします — ですが、この原則はどんな高コストなレーンにも当てはまります。

高コストなレーンは、次の2つでゲートしましょう。

  1. PRのベースブランチ。 重いスイートは、リリース/ラウンドのPR — ベースが main のもの — でだけ走らせ、統合ブランチへ流し込むすべてのトピックブランチPRでは走らせません。エージェント駆動のフローでは大半がトピックPRであり、重量レーンをそこから外すだけでコストの大部分が消えます。このルールが前提とする具体的なブランチトポロジーとCIワークフローについては、リリースラウンド § 重いCIをベースブランチでゲートするを参照してください。

  2. 夜間の schedule:。 ベースブランチのゲートを、スケジュール型のフル実行でバックアップし、トピックPRをスキップしたものが長く未検証のまま残らないようにします。これは、T3の定期再試験ティアがその役目を果たしている姿です。

レーンを速くするには大きなランナーに手を伸ばし(ルール1)、レーンを安くするにはトリガー設計に手を伸ばす(ルール4)。この2つを混同すること — PRごとの重量レーンをPRごとから完全に外すのではなく、ダウンサイズして節約しようとすること — は、端数の誤差を最適化して請求額を無視することです。

pnpm -r はトポロジカルなチャンクで走る

上の4つのルールはどれも、ジョブの形があなたの思っているとおりである、という前提の上に立っています。テストレーンが1本の pnpm -r run test であるモノレポでは、その前提はたいてい同じ場所で崩れ、しかもそれはあらゆるサイジング判断より上流で起きます。pnpm -r は全パッケージを一斉にファンアウトしません — トポロジカル順のチャンクで実行し、前のチャンクが丸ごと掃けるまで次のチャンクは始まらないのです。 つまり遅いパッケージ1つは、単に遅いパッケージなのではなく、バリアです。実測では、55パッケージのワークスペースで7分かかるワーカーテストのスイートが無関係な12パッケージをその後ろに直列化し、ファンアウト全体の実効並列度は12コアのマシンで1.27倍でした — 並列作業のために買ったマシンが、実行のほぼ全体を一度に1つずつ処理して過ごしたわけです。

この比率こそが診断であり、実行がすでに出力している数字の算術で求まります。

effective parallelism = Σ(per-package wall time) ÷ (run wall time)

55 packages, 12 vCPU available  →  1.27×   ← cores bought, not used

多コアのランナー上でこの比率が1倍近いなら、そのレーンはバリア律速です — そしてこれは、ルール1の相殺が、ルール1のカバーしていない方向から崩れている姿です。ワークロードは本当に並列化可能で、ワーカー数をピン留めしているものもありません。それでもコアは何も買えません。ワーカーの上にいるスケジューラーが、チャンク同士を重ねることを拒むからです。モノレポのテストレーンを大きくする前に、この比率を確認してください。 さもなければ、大きなランナーは並列ジョブの衣をまとった「純粋な追加コスト」のケースになります。

深刻度はパッケージ数にスケールする

1.27倍という数字を、それが説明していないワークスペースへ持ち込んではいけません。同じ計測を、実依存のエッジがちょうど1本だけの4パッケージのワークスペース — つまり最大でも2チャンク — で行うと、37.7秒の実行のうち4.41秒がアイドル(11.7%)、2回目の実行では36.8秒のうち3.18秒(8.6%)でした。そこでは1パッケージが実測時間の80%を占めるので、バリアを完全に取り除いても38秒のうち約4秒しか縮みません。誰も予定に組み込むべきでないリファクタリングです。

パッケージが増えればチャンクが増え、各チャンクで足止めされるパッケージも増えるので、このパターンの深刻度はパッケージ数にスケールします。55パッケージではレーンの支配的なコストであり、4パッケージではノイズです。何かを投じる前に、自分の比率を計測してください。

2つのレバーと、プロセスごとの起動税

比率がバリア律速だと告げたら、抜け道は2つあります。

バリアになっているパッケージを除外する。 遅いパッケージがすでに専用のCIジョブを持っているなら、ファンアウトの中でも走らせることは、その分を二重に払った上に、後続すべてを直列化していることになります。

pnpm -r --filter '!@scope/worker-tests' run test

あるいはチャンク分割そのものを無くす。 ワークスペースのルートにスケジューラーを1つ置く、つまり test.projects が全パッケージを覆うルートのvitest設定へ移すことです。スケジューラーが1つなら、ワーカーに配られる単位はテストファイルであり、待つべきパッケージのチャンクはもう存在しません。設定の形はプロジェクトレベルのVitest設定を参照してください。

2つめのレバーには、1つめには無いおまけが付きます。pnpm -r run test はパッケージごとにテストランナーのプロセスを1つ起動し、その起動はテストが1件も走らないうちにパッケージあたり約0.6〜1秒かかります。55パッケージなら、毎回の実行で約1分がプロセス起動だけに消え、これはランナーのサイズをいくら上げても減りません。ルートのスケジューラー1つなら、この税は一度きりです。

バリアを外す前に、そのエッジが本物か確認する

実依存のエッジがあるところでは、トポロジカル順はスループットのコストであるだけでなく、正しさの前提条件です。実測のケース:あるパッケージのTypeScript宣言生成パスが兄弟パッケージの型をimportしているため、依存する側を先にビルドすると、遅く走るのではなく TS2307 で即座に失敗します。当のワークスペースが WORKSPACE_BUILD_ORDER 定数をハードコードし、それを順序の唯一の情報源として扱っている — 呼び出しがたまたま生む順序を信用しない — のは、まさにそのためです。

Warning

バリアを消す前に、それを分類してください。 1つのパッケージが遅いという理由だけで存在するバリアはスループットの問題であり、上のレバーが当てはまります。依存する側がその依存のビルド成果物を消費するという理由で存在するバリアは、構造を支えています — そしてトポロジカル順をスループット最適化として捨てることは、遅いビルドを壊れたビルドに変えるのです。

タイミングの振り返りはジョブレベルのタイムスタンプを読む

このページのすべてのルールは、ある計測の下流にあります。そして最も手軽に得られる計測こそが、間違ったものです。gh run list が報告するのはワークフローレベルの createdAt → updatedAt であり、その区間にはその実行がランナーを待っていた時間が含まれています。 キューの待ちと実行は持ち主の異なる別々のシグナルであり、それらを黙って足し合わせた数字は、上のどの判断も支えられません。

このずれは些細ではありません。実測では、2つの実行が開始前に72〜73分キューで待っていました。ワークフローレベルで見ると、そうした実行の中のまったく正常な62分の実行が、134分の瀕死の実行として読めてしまいます — 実際の所要時間が1秒も動いていないレーンのサイズを、誰かが変えに行く数字です。

短いジョブほど、比率としての歪みは大きくなります。サンプリングしたある実行では、7秒の純Node製のガードジョブが、ランナーを303秒待っていました。ワークフローレベルでは、この待ちは作業と区別が付きません。実際の作業が232秒だったゲートにおける、5分の劣化として読めてしまうのです。

実行リストではなく、jobsエンドポイントを読む

正しい計測はジョブレベルの startedAt → completedAt であり、実行のjobsエンドポイントがジョブごとにそれを報告します。

gh api repos/{owner}/{repo}/actions/runs/<run-id>/jobs \
  --jq '.jobs[] | {name: .name, started: .started_at, completed: .completed_at}'

測り直しがいつも主張を覆すわけではありません。「約3分」と文書化されていたe2eジョブをこの方法で測り直すと、212秒と242秒でした — 主張は生き延びたのです。それでも測り直す価値はありました。そこで壊れていたのは数字ではなく、手順(プロトコル)だったからです。

実行を集計するものはすべて同じ欠陥を受け継ぎます。予算ダッシュボード、キャンセル率の統計、そしてとりわけ「実行のX%がタイムアウト上限に達している」という主張は、判断を動かす前にジョブレベルで再検証する必要があります。 ワークフローレベルのタイムスタンプから計算した上限到達率は、キューが長かっただけの実行を、実際には起きていないタイムアウトとして数え、そしてまさに間違ったレバー — そもそも遅くなかったレーンに、より大きなランナーを — を指し示します。実行ティアの「10分未満」というPRゲートの予算も、同じ意味で実行の予算です。ジョブレベルで測ってください。さもないと、自分では制御できないキューに対してレーンをチューニングすることになります。

出典をピン留めしていないタイミングの主張は、静かに劣化する

出典の書かれていない数字は、静かに腐ります。上の「約3分」という主張はタイムスタンプの出典を名指ししておらず、正しかったのはたまたまでした — そして次にそれを更新する人は、手軽な gh run list に手を伸ばし、同じ文の下にワークフローレベルの数字を書き、そして何も失敗しないでしょう。数字の隣に出典をピン留めしてください。 どのエンドポイントの、どのフィールドの組を、何サンプル測ったのか、です。

シグナルを分けたままにしておくことが、時間をまたいだ比較を可能にします。これらの数字の元になった対照実験は、まさにその理由でキュー待ち、セットアップ、ジョブ実行を別々に記録しており、そしてそれらのシグナルは実際に独立して動きました。ジョブごとのキュー待ちの最大値は変更前が 83/17/13/14/11 秒、変更後が 18/14/12/14/15 秒だった一方、クリティカルパス上のジョブの最大値は終始 313〜484 秒という同じ帯に収まっていました。ワークフローレベルの1つの数字に混ぜてしまえば、このキューの改善は、誰も加えていない実行時間の変化として読めていたはずです。

ルール3は、サイズを変える前に失敗モードを診断せよ、と言います。この2つのセクションは、その下に置かれる一文です。これから変えようとしているものを測る数字で診断すること — 測るべきはレーン自身の実行時間であって、その上にいるスケジューラーでも、その前にあるキューでもありません。

これらの計測の上に載る実験プロトコルの全体 — どの時計を対象にするのか、キープか差し戻しかを何本の同一SHAサンプルで決めるのか、そして速くなったゲートが依然として同じテストを走らせていることをどう証明するのか — は、CIゲートの最適化にあります。

まとめ

症状誤った直感正しいレバー
そもそもCIをどこで走らせるかどのリポジトリにも同じプロバイダーパブリック → 無料の標準GitHubホスト型。プライベート → 限界分単価でBlacksmith(プロバイダー選択が最初)
パブリックリポジトリの標準ランナーが遅く感じる有料プロバイダーや大きなランナーへ移る意図して速度を買うのでない限り無料の標準形状に留まる — 大きなランナーはパブリックでも課金される
PRゲートが遅く感じ、作業は並列化可能放っておく。コアは金がかかるレイテンシのために大きく(ルール1) — 実効並行数がコアにスケールする間はコストほぼ据え置き(まず workers のピン留めを確認)。ただし名指しの理由があるときだけ(既定は最小形状)
vitest/jsdomのヒープOOMRAMを増やすため2→4 vCPUvitest --shard(ルール2) — サイズでは直らない
e2eスペックが負荷でタイムアウトflakyと決めつけてリトライを足すまず診断:CPU飢餓 → 2→4 vCPU、ただし競合が消えるまで(ルール3)、コネクション飢餓 → アプリ/配信レイヤーで修正(サイズ変更では直らない)
月次CI請求額が高すぎるランナーをダウンサイズベースブランチ+スケジュールでレーンをゲート(ルール4)
実GPU/macOSカバレッジが必要Macランナーで、PRごとに走らせるスケジュール型T3 macOSレーン(約 110/月、約 1,400/月ではなく)
多コアのランナーで pnpm -r のファンアウトが直列とほとんど変わらないコアを増やすチャンクのバリアを探す — --filter '!pkg'、あるいはルートの単一スケジューラー。ただしそのエッジが実依存でないことを先に確認する
タイミングの振り返りで、あるレーンが数分遅くなって見えるそのレーンをリサイズ/再チューニングするジョブレベル(startedAt → completedAt)で測り直す。ワークフローのタイムスタンプにはランナー待ちのキューが含まれる

一貫する筋道:ランナーのラベルはトークン2つ分の編集だが、それをうまく選ぶとは、まず律速の制約に名前を付けることを意味する。 レイテンシ、メモリ、CPU競合、総コストは4つの異なる問題であり、4つの異なるレバーを持ちます。そして大きなランナーは、そのうちちょうど2つ — レイテンシ(ルール1)とCPU飢餓(ルール3) — に対する正しい答えであり、どちらの答えも同じ設定ノブ、すなわち実効ワーカー数に縛られているのです。

参考リンク

  • GitHub Actionsの課金について — パブリックリポジトリの標準ランナーは無料というポリシーと、「大きなランナーは常に課金される」という注意点(2026-07-05検証)。

  • Blacksmith ランナー概要 — ラベルの全マトリクス、OSイメージ、無料枠/消費倍率の詳細(2026-07-05検証)。

  • Blacksmith 料金 — 形状ごとの分単価(2026-07-05検証)。

  • 実行ティア — これらのサイジングルールが仕える T0–T4 モデル。

  • 重いテストの判断ルール — 環境/プラットフォーム的に実行不能なテストが、スケジュール型のmacOSティアへどう到達するか。

  • 定期再試験 & 夜間試験 — スケジュール型(T3)のmacOSジョブが実際にどう組まれるか。

  • CIゲートの最適化 — これらのタイミングのルールが供給する受け入れプロトコル。時計に名前を付けること、3本の中央値というサンプルのルール、そして速くなったゲートが静かにカバレッジを失っていないことのアイデンティティによる証明。

最適化の範囲

同じスイートの高速化では、件数だけでなくcanonical test identitiesと実環境の証拠を維持します。置換・統合には保証の対応、頻度低下・退役にはライフサイクルの検知時期・リスク判断が必要です。AIスイート精査は選択肢を比較し、課金実行量・待ち時間・概算を区別します。変更範囲に応じた検証は選択と結果再利用を扱います。一時的なredを許容する開発でマージ待ちを外しても、それだけでrunner費用は減りません。

Revision History

作成更新