Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
go test を速くする
Search
sivchari
August 24, 2026
420
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
go test を速くする
sivchari
August 24, 2026
More Decks by sivchari
See All by sivchari
govalid ~ Type-safe validation tool ~
sivchari
0
170
Go1.25 リリースパーティ ~ nil pointer bug ~
sivchari
0
160
Google Developer Group - DevFest Tokyo 2025
sivchari
0
170
Who tests the Tests ?
sivchari
0
210
Go 1.26 リリースパーティ
sivchari
0
240
静的解析 x Kubernetes API Conventions = Kube API Linter ~ ベストプラクティスに準拠したカスタムリソースの作り方と運用 ~
sivchari
0
250
What's GOCACHEPROG ?
sivchari
1
600
gh_extensionsによる快適なOSS生活.pdf
sivchari
0
200
Visualization Go scheduler by gosched-simulator
sivchari
1
700
Featured
See All Featured
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
390
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
430
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
Exploring anti-patterns in Rails
aemeredith
3
470
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
620
Navigating Weather and Climate Data
rabernat
0
490
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
Building AI with AI
inesmontani
PRO
1
1.2k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.6k
Practical Orchestrator
shlominoach
191
12k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
Transcript
go test を速くする 2026-08-24 layerx.go #5 @sivchari
whoami @sivchari LayerX バクラク事業部 (2026 -) CI/CD・AI Agent 基盤などの開発基盤を担当 Go
Conference 主催 / golang.tokyo 運営 OSS kumo Kubernetes maintainer ArgoCD Envoy Gateway Kubernetes Contributor Award 2025 受賞 © LayerX Inc.
前提 LayerX の Go monorepo バクラクを支える巨大な Go monorepo 数千の package、多数のサービスが同居
PR のたびに全 package を go test するのは現実的ではない そこで affected package testing 変更されたファイルから「影響を受ける package」だけを算出してテストする 2023 年から存在する仕組み (by @izumin5210) © LayerX Inc. 3
前提 affected package testing の仕組み PR の変更ファイル一覧 git diff /
GitHub API ファイル → 所属 package 起点 package 集合 import の逆依存を辿る 影響を受ける package 集合 対象ディレクトリ配下だけに絞る go test $packages 影響のない package は実行しない © LayerX Inc. 4
入社時点のボトルネック 検出のために go list が走っていた 旧実装は golang.org/x/tools/go/packages (= go list
のラッパー) NeedImports は 3rd party module まで含めて依存を解決する go mod download か module cache の restore が事実上必須 「テストを減らすための検出」のために module 解決を待つのは本末転倒 © LayerX Inc. 5
観察 本当に必要な情報はなにか 欲しいのはリポジトリ内の import グラフだけ 3rd party の変更は go.mod /
go.sum の変更として別途扱えばよい グラフ構築時にリポジトリ外の import は捨てるだけ 型チェックも、ビルドも、module 解決も要らない Go の import 関係は ソースコードの import 宣言だけで決まる import 宣言は AST の入り口にある。そこまで読めば止まっていい © LayerX Inc. 6
解決 1 | AST 時点で落とす
解決 1 | AST 時点で落とす go/build.ImportDir だけで import を集める 各
.go を go/parser の parser.ImportsOnly モードでパースする package 節と import 宣言を読んだら、本文を読まずに即 return 型チェックも、コンパイルも、module 解決もネットワークも一切起きない 標準ライブラリだけで完結。 go list の起動すら不要 © LayerX Inc. 8
解決 1 | AST 時点で落とす 実装: ディレクトリを歩いて import を集める 本体の
import もテストの import も、区別せず同じグラフに入れる テストだけが使う package の変更も、同じ仕組みで拾える あとは変更ファイルの package から逆依存 map を辿るだけ © LayerX Inc. 9
解決 1 | AST 時点で落とす 結果: 検出は数十秒で終わる sparse checkout +
affected 検出まで含めて数十秒で完了 go mod download も module cache の restore も不要 「対象外 package のビルド (型チェック) すらしたくない」を実現 コンパイルの検証は別 workflow (go-build) が全量で担保する役割分担 検出側は import 解析だけに専念できる © LayerX Inc. 10
それでも test job は毎回数分かかっていた
解決 2 | パッケージ構成で依存を切る
解決 2 | パッケージ構成で依存を切る 前提: 全 service 入りの universal バイナリ
バクラクは複数 service からなる分散構成 service ごとにバイナリをビルド・デプロイするのは手間が大きい そこで全 service を 1 つに束ねた universal バイナリを採用 エントリポイントの server package が全 service を import する 起動時の指定で「どの service として動くか」を切り替えられる 詳細: connect-go による複数サービスの開発とユニバーサルバイナリによる改善 © LayerX Inc. 13
解決 2 | パッケージ構成で依存を切る server package の中身: 自動生成された「対応表」 server package
の中にある自動生成ファイル (コード上の名前は registry) 名前から初期化関数を引く表 → そのために全 service を import する 中身は import と map リテラルだけで、分岐もロジックも無い © LayerX Inc. 14
解決 2 | パッケージ構成で依存を切る 原因: 全 service を import する「対応表」
server package に同居していたもの 全 service を import する対応表 (自動生成) server 起動の logic テスト 逆依存グラフのハブになっていた どの service を 1 つ変えても affected に入る 全 service を埋め込むテストバイナリの link に数十秒 かかり、 しかもキャッシュが効かない © LayerX Inc. svc A svc B … svc N server package 対応表 + logic + テスト どの service を変えてもここが affected 15
解決 2 | パッケージ構成で依存を切る 対応表を、テストを持たない子 package に切り出す 対応表にテストしたいロジックは無い。問題はテストと同居していたこと Before server
package = 対応表 + logic + テスト → テストが全 service を link する After 対応表はテストを持たない子 package へ server package は logic のみ (service import ゼロ) テストは fake の対応表を注入して数秒に © LayerX Inc. ↓ 16
解決 2 | パッケージ構成で依存を切る package を分けただけで、link が消えた affected 検出のコードは 1
行も変えていない テストを持つ package が、全 service を import しなくなった 全 service を link するテストバイナリが、そもそも作られない テストを持たない package は compile されるだけ (build cache が効く) 効果 全 service の link 数十秒 → compile のみ数秒 test job の中央値が分単位で短縮 © LayerX Inc. 17
解決 3 | テストの無いビルドを消す
解決 3 | テストの無いビルドを消す テストの有無は、import を集めた時点で分かる go/build.Package はファイルと import を役割ごとに別フィールドで返す
フィールド GoFiles / Imports TestGoFiles / TestImports XTestGoFiles / XTestImports ImportDir © LayerX Inc. 中身 package 本体 package foo のテスト (internal test) package foo_test (external test) の戻り値に「テストを持つか」の情報が最初から含まれている 19
解決 3 | テストの無いビルドを消す テストが無い package はテスト対象にしない 追加の走査ゼロ・ go list
実行ゼロで判定できる 対応表は全 service を import したままなので、分離後も必ず affected に入る ただしテストを持たないので、入っても「コンパイルするだけ」 affected から除外し、コンパイル検証は go-build workflow に任せる テストを持たない package だけの run は test job を丸ごとスキップ 大型 runner + MySQL/Postgres/Redis 起動込みの数分が消える © LayerX Inc. 20
解決 4 | キャッシュの取り方を変える
解決 4 | キャッシュの取り方を変える tar を展開し終わるまで、テストは始まらない affected を絞っても、残ったテストは build cache
を復元してから始まる actions/cache 方式 = 数 GB の tar を実行前に全部展開する 実測 100s の内訳は、ダウンロード 4s に対して展開が 95s GOCACHEPROG なら、必要なものだけをその場で取れる © LayerX Inc. 22
解決 4 | キャッシュの取り方を変える リクエストは並行に飛んでくる GOCACHEPROG に渡すのは、自分で書いたプログラム go build は多数の
goroutine から並行に get / put を投げてくる リクエストには ID が振られており、レスポンスは順不同で返してよい (protocol の仕様) なので、リクエストごとに goroutine を起こして並行に処理できる © LayerX Inc. 23
解決 4 | キャッシュの取り方を変える 常駐プロセスを 1 つ立てて、S3 を集約する GOCACHEPROG は
go コマンド 1 回につき 1 プロセスだけ起動される テストは go run で wrapper を起動し、その中から go test を呼ぶ構成 1 ステップの中で go が何度も起動する。そのままだと毎回 S3 に繋ぎ直すことになる そこで役割を 2 つに分けた は CI job に 1 つだけ常駐し、S3 と手元の cache を持つ。起動直後から先読みする connect は GOCACHEPROG として起動され、socket で serve に取り次ぐだけ serve serve は go test の前のステップで起動する。そこから先読みが始まる warm job build cache を 64 個のチャンクに分ける © LayerX Inc. → S3 manifest + チャンク (tar.zst) × 64 → serve (常駐 1 つ) S3 と手元の cache を集約 → connect × 多数 go test の各プロセス 24
解決 4 | キャッシュの取り方を変える 分割で転送量は減らない。効いたのは待ち時間 Go は build cache を
action ID の先頭 1 バイトごとのディレクトリに置く (00〜ff の 256 個) warm job はこれを 4 個ずつまとめ、64 個のチャンクにして S3 に置く action ID は SHA-256 なので、どのディレクトリに入るかは一様に分布する 数千件のエントリを引けば 256 個すべてに当たる → ほぼ全部のチャンクが要る つまり転送量は tar 方式と変わらない 効いたのは、ダウンロード (ネットワーク) と展開 (CPU) をパイプライン化できること チャンクがその単位。細かすぎると 1 つあたりの overhead、粗いと並行度が落ちる 実測で 64 に決めた (32 では 293s、64 で 270s) © LayerX Inc. 25
効果 test job の実行時間 (P50 / P90) 解決策のリリース以降、Job ごとの P50
/ P90 がともに短縮 © LayerX Inc. 26
まとめ go test を速くする = やらないことを増やす 検出を速くする: 汎用ツールの便利さの代償を知る 欲しいのが import
グラフだけなら、AST の入り口だけ読めばいい グラフを小さくする: 逆依存のハブはパッケージ構成そのものを直して潰す 対象を減らす: 言語が構造として持つ情報を使い切る Test / XTest が分かれているので「テストの無い package」を追加コストなしで判定できる 全部展開しない: キャッシュも要る分だけその場で取る 1〜2 割の PR では test job 自体が走らない。走る分も同条件の実測で 373s → 270s © LayerX Inc. 27