の費用は、基本的に実行時間に比例します。つまりコスト削減とは実行時間の削減であり、EP.3 で扱った高速化とほぼ同じ話です。ただしひとつだけ、高速化にはない選択肢があります。そもそも流さないという選択です。
この回は、効果の大きい順に手段を並べます。設定だけで済むものから始め、構成の見直しへ進みます。
1. まず、何にかかっているかを見る
削減の前に内訳を出します。どのワークフローが、どれだけ時間を使っているかが分かれば、手を付ける場所は自動的に決まります。実行履歴は API から取得できます。
# 直近100件の実行を取得(gh CLI)gh run list --limit 100 --json name,conclusion,startedAt,updatedAt \ > runs.json
# 失敗した実行だけを見るgh run list --limit 100 --status failure --json name,displayTitleimport jsonfrom collections import defaultdictfrom datetime import datetime
runs = json.load(open("runs.json"))agg = defaultdict(lambda: {"n": 0, "sec": 0.0, "fail": 0})
for r in runs: start = datetime.fromisoformat(r["startedAt"].replace("Z", "+00:00")) end = datetime.fromisoformat(r["updatedAt"].replace("Z", "+00:00")) a = agg[r["name"]] a["n"] += 1 a["sec"] += (end - start).total_seconds() if r["conclusion"] == "failure": a["fail"] += 1
for name, a in sorted(agg.items(), key=lambda x: -x[1]["sec"]): avg = a["sec"] / a["n"] / 60 print(f"{name:28s} 合計{a['sec']/60:7.1f}分 平均{avg:5.1f}分 " f"実行{a['n']:3d}回 失敗{a['fail']:3d}回")見るべきは合計時間です。1回が長いワークフローより、短いが何度も走るもののほうが総量では上回ることがよくあります。平均だけを見ていると、この構造を見落とします。
あわせて失敗率も出しておきます。失敗が多いワークフローは、その回数ぶんだけ再実行されている可能性が高く、見えている時間の何倍かを消費しているためです。特に が混ざっていると、費用の観点でも無視できない量になります。EP.11 で扱う不安定さの問題は、品質だけでなく金額としても現れるわけです。
2. 止められる実行を止める
最も効果が大きく、副作用が小さいのがここです。テストの中身を一切変えずに削減できます。
| 手段 | 効果 | リスク |
|---|---|---|
| 古い実行を打ち切る(`concurrency`) | 大きい | ほぼ無し |
| タイムアウトを設定する | 異常時に大きい | 上限が短すぎると誤って落ちる |
| 変更のないパスでは流さない(`paths`) | 大きい | 判定漏れに注意 |
| 下書き状態では流さない | 中 | 作業中の確認ができなくなる |
| 定期実行の頻度を見直す | 中 | 検知が遅れる |
on: pull_request: paths-ignore: - '**.md' - 'docs/**' - '.vscode/**'
# 下書きのプルリクエストでは流さない場合jobs: test: if: github.event.pull_request.draft == false runs-on: ubuntu-latest`paths-ignore` は効果が大きい反面、除外したパスが実は影響していた場合に検知漏れが起きます。設定ファイルやスキーマ定義は、一見ドキュメントに見えて挙動に影響することがあります。除外は明確に無関係なものだけに留めてください。
3. 実行する範囲を絞る
次が、流すけれども全部は流さないという手です。EP.3 で扱った変更範囲での絞り込みを、CI 上で使います。
ここで重要になるのが、プルリクエストの段階では絞り、マージ時には全部流すという二段構えです。反復の速さと最終確認の確実さを両立できます。絞り込みによる取りこぼしは、マージ前に必ず回収されます。
| タイミング | 流す範囲 | 狙い |
|---|---|---|
| プルリクエスト更新のたび | 変更範囲+高速層 | 速い反復 |
| マージ直前 | 全件 | 取りこぼしの回収 |
| 定期実行(夜間など) | 全件+重い検証 | 時間制約がない枠を使う |
3番目の「定期実行」は費用対効果が高い枠です。急がない検証(、大きな評価セット、依存の脆弱性走査)をここへ寄せると、日中の待ち時間を増やさずに検証を厚くできます。ここは待ち時間の制約が無いぶん、普段は重すぎて回せない検証を置ける枠でもあります。
ただし定期実行には固有の弱点があります。失敗しても、誰も見ていない時間帯に起きるという点です。通知の宛先を決めずに定期実行だけ増やすと、赤が積み上がったまま気づかれません。定期実行を足すときは、必ず通知先と対応の担当をセットで決める。これを守らないと、費用だけ増えて検知力は増えない状態になります。
4. 実行環境の選定
ここまでやって、なお実行時間が多い場合に検討するのが実行環境です。 に移すと、実行時間に応じた課金を回避できます。
ただし、これは費用を管理コストに置き換える選択です。マシンの用意と更新、障害対応、そしてセキュリティの責任を負うことになります。特に、外部からのプルリクエストを自前のマシンで実行する構成は危険で、任意のコードが自分たちの環境で動くことになります。
- 実行時間が継続的に多い — 一時的な増加なら移行の手間に見合わない
- 管理を担える体制がある — 誰も面倒を見ないランナーは必ず壊れる
- 外部貢献の扱いを決められる — 自前ランナーで動かさない構成にできるか
- キャッシュの効果が大きい — 同じマシンを使い回せる利点は実行時間にも効く
実行環境の変更は影響範囲が広い判断です。打ち切り・絞り込み・キャッシュで削れるところを削ってから検討すると、そもそも移行が不要だったというケースも珍しくありません。
5. 削ってはいけないもの
費用削減には、やってはいけない削り方があります。短期的には数字が下がるものの、後で高くつく類のものです。
| やりがちな削減 | 何が起きるか |
|---|---|
| 失敗しても続行する設定にする | 赤が無視される。テストが機能しなくなる |
| 不安定なテストを無効化して放置 | 検知力が静かに落ちる。EP.11 で扱う |
| 重要な経路のテストを定期実行に回す | 壊れてから気づくまでが長くなる |
| 証跡の保存をやめる | 失敗しても原因が分からず、調査に時間がかかる |
共通するのは、費用は見えるが、失われた検知力は見えないという点です。削減の議論では、削った結果何を検知できなくなったかを必ず併記してください。数字だけの比較は、必ず削りすぎの方向へ寄ります。
6. 優先順位の整理
この回の内容を、着手順に並べ直します。上から順にやるのが最も効率的です。
- 1内訳を出す — どこにかかっているかを知らずに削らない
- 2打ち切りと上限を設定する — 設定だけ。副作用がほぼ無い
- 3キャッシュを効かせる — 効いているつもりで効いていないことが多い
- 4明確に無関係なパスを除外する — 慎重に。判定漏れに注意
- 5タイミングで範囲を分ける — 反復は絞り、マージ前に回収する
- 6急がない検証を定期実行へ寄せる — 日中の待ち時間を増やさずに厚くできる
- 7最後に実行環境を検討する — 費用を管理コストに置き換える判断
1から3までは、ほとんどの現場でその日のうちに実施でき、効果が出ます。ここを飛ばして環境の議論から入ると、労力の割に成果が出にくくなります。
費用は実行時間に比例する。まず内訳を出し、止められる実行を止める。次にキャッシュと範囲の絞り込み。実行環境の変更は最後で、費用を管理コストに置き換える判断だと理解しておく。削減の議論では、失われた検知力を必ず併記する。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。