ストレスフリーで高速なCIテストのために工夫したこと

この記事はTVer Advent Calendar 2025 10日目の記事です。9日目の記事はta9tさんの「toC×エンタメPMとして「欲しい」を理解し続ける」でした。

はじめに

こんにちは、Backend Enabling Teamの伊藤(@sou_world) です。

TVerのバックエンドはGoで書かれておりGitHubのPRを利用して開発をしています。PRはコードレビューだけでなくテストやlint、labelチェックなどの様々なCIを通すことで初めてマージできます。

ここ1年の開発チームの拡大と開発速度の上昇に伴い、CI実行時間が徐々に増加し開発体験に影響が出始めてきました。なかでもテスト実行がボトルネックで、去年比で2倍以上遅くなっておりcommitごとに15分以上実行がかかる状態になっていました。

開発組織の拡大は良いことですが、その結果個々人の開発サイクルが遅くなってしまっては本末転倒です。なんとか速くしたいです。

今回はGitHub Actions上で実行しているGoのテストを高速化するために工夫したことについて紹介します。

改善前のCIテストについて

実行環境と所要時間

GitHub Hosted Runner (Linux x64, 2 CPU, 7 GB RAM) を使用。

  • キャッシュなし: 約15-20分
  • キャッシュあり: 約8-10分

実行フロー

  1. PRのopen/commit/mergeが実行されるとgo test用のCIが発火される
  2. GOCACHEおよびGOMODCACHEをCommit単位で取得しに行く
  3. go test -race ./... を実行する

また、最新のmainを取り込めていないとマージできない制約をGitHubの設定で行っていました。そのためmainへのマージタイミングがメンバー間で重なると、マージできるまで何度も実行し続けなければならない状況でした。

改善のためにやったこと

merge queueの導入

何よりもストレスだったのがmainへの取り込みです。 mainへのマージは最新のmainが取り込まれていないと実行できないように設定されています。

最新のmainを取り込んでCIが終わるまで10分以上待ってその後マージボタンを押します。この3ステップを全て手動で実行しなければいけません。この時点でもちょっと嫌な雰囲気が出ています。

このCI実行中に別のPRがマージされる場合はもっと大変です。 再び最新のmainを取り込んでCIの完了を待ってマージをしなければなりません。1回でも苦痛ですが、さらに運が悪いとこれが数回起こる可能性もあります。最悪の開発者体験です。

これを解決すべくmerge queueを導入しました。各開発者は従来の面倒な作業の代わりにボタンを一個押すだけで済むようになりました。

-raceフラグによるオーバーヘッドの解消

Goのテストでは-raceフラグを使用することで、データ競合を検出できます。これは並行処理のバグの早期発見に有用な機能ですが、テスト実行時間を増加させる要因となります。

TVerではCIテストの構築時はすべてのテスト実行で-raceを有効にしていました。当時はコードが小さく実行時間も十分許容でき、全てのテスト実行で -race をつけることの利益の方が高かったためです。 しかしながら開発組織の拡大に伴い -race は大きなボトルネックとなりました。

現在はmerge queueとmainへのマージ時のみ-raceを有効にしています。開発の中心であるPRへのcommitではCI速度を優先し、mainへのマージ前後では品質の担保を優先する形にしました。

これにより約30%の短縮を実現でき15-20分から10-15分の実行に短縮できました。

キャッシュ戦略の最適化

GitHub Actionsでは10GBまでキャッシュを利用できます。GOCACHEはPRの各commit単位で作成していましたが、10GBを数GB超過している状態でした。 そのためGOCACHEをPR単位で作成する形へ変更し、キャッシュを10GB以内に収めました。

これによってキャッシュが各PRで確実に利用できるようになり、各PRの2回目以降のCI実行時間の確実な短縮を実現できました。

ActionsのRunnerのスペックを上げる

Private RepositoryでGitHub Actionsを利用する場合、デフォルトでは2CPUのRunnerまでしか利用できません。

TVerのバックエンドではリアーキテクチャを進めており新旧アーキテクチャが混在している状況で、かなりのpackageが存在している状態です。 go test ./...の各パッケージのテストを同時にたくさん実行できれば、より実行時間を短縮できます。そこでRunnerのスペックを上げることを検討しました。

Self-hosted RunnerやSaaSを利用する選択肢もありましたが、今回はGitHubが提供しているLarge runnerを採用しました。 DevOpsは現状2人で取り組んでおり利用したいCIも1つだったため、導入と管理が簡単であることを最優先としたためです。

現在は8CPUのRunnerを利用していますが、これによって実行時間は50%改善できました。結局スペックは正義ですね。

改善後のCIテストについて

実行環境と所要時間

GitHub Hosted Runner (Linux x64, 8 CPU, 32 GB RAM) を使用。

  • キャッシュなし: 約5-8分 (改善前: 15-20分 → 約60-70%短縮)
  • キャッシュあり: 約2-4分 (改善前: 8-10分 → 約60-75%短縮)

実行フロー

PRのopen/commitの場合

  1. go test用のCIが発火される
  2. GOCACHEおよびGOMODCACHEをPR単位で取得しに行く
  3. go test ./... を実行する

PRのmergeの場合

  1. go test用のCIが発火される
  2. go test -race ./... を実行する

merge queueを利用しており、各開発者はボタンを1回押せばPRのマージ作業が完了できるようになりました。

終わりに

今回のCIテストの改善ではテスト品質を維持しながら開発体験を向上させることを目指しました。 GitHubが提供している機能の活用、ビルドキャッシュの最適化、マシンスペックの変更など基本を抑えることで速度と開発者体験を向上させられます。

似たような課題を抱えているチームの参考になれば幸いです。

明日は西尾さんの「TVerインフラアーキテクチャの現在地」です。