miive プロダクトブログ

miiveのプロダクトチームのブログです。

Goビルドを理解し、CI/CDの処理時間を25分から10分に短縮した話

Go Conference mini で登壇してきました!

miiveの @sato-shin です。 2026年2月21日に仙台で開催されたGo Conference miniで登壇してきました!

参加から1ヶ月経ってしまいましたが、今さらながら登壇した内容を紹介いたします!

発表スライドはこちらです。 speakerdeck.com

1. はじめに

miiveではバックエンド開発にGoを採用しています。サービスリリースから3年が経つ中で、CI/CDの処理時間は徐々に増加し、1回あたり約25分を要する状態になっていました。

「CI/CDが遅い」というのは、開発者の生産性に直結する問題です。PRを出してからマージまでの待ち時間が長くなり、開発のリズムが崩れてしまいます。

本記事では、この25分を10分にまで短縮した改善の過程をご紹介します。

2. miiveのCI/CDの構成

改善前のCI/CDパイプラインは以下のような構成でした。

Unit Test / E2E Test / Lint は並列で実行され、その後にBuild、Deployと直列で続きます。特に Buildの14分 が大きなボトルネックでした。

3. Goのビルドの特徴をおさらい

改善に取り組む前に、まずGoのビルドの仕組みを整理しました。Goのビルドには以下の特徴があります。

3.1. 単一バイナリで動作する

Goはビルド後に単一の実行ファイルを生成します。Javaのようにランタイム(JVM)を必要とせず、バイナリをそのまま実行できます。これにより、依存するものが少なく、管理が楽でセキュアであり、サーバーの立ち上がりも高速です。

3.2. クロスコンパイルが可能

GOOSGOARCH を環境変数で指定するだけで、異なるOS・アーキテクチャ向けのバイナリをビルドできます。CI環境がUbuntuでも、Linux/amd64向けのバイナリを簡単に生成できるため、CI/CDの設定がシンプルになります。

$ GOOS=linux GOARCH=amd64 go build -o app

3.3. ビルドキャッシュが組み込まれている

Goのビルドは以下の3ステップで行われます。

  1. 依存関係の解決
  2. コンパイル (go tool compile) - パッケージごとにオブジェクトファイルを生成
  3. リンク (go tool link) - オブジェクトファイルを統合して実行ファイルを生成

ここで重要なのが、パッケージごとのオブジェクトファイルがキャッシュされるという点です。ソースコードに変更がないパッケージは、キャッシュから読み込まれるためコンパイルがスキップされます。

4. 改善への取り組み

4.1. 一般的な高速化手法の検討

まず、Goビルドの一般的な高速化手法を調査しました。

手法 結果
CIでビルドキャッシュを利用する 2回目以降は速くなる
CIでモジュールキャッシュを利用する 2回目以降は速くなる
利用していないコードを削除する 調査したが、全体の1%にも満たない
パッケージの分割を最適化する 時間がかかりすぎるため見送り

キャッシュの利用は有効ですが、それだけでは根本的な解決になりません。キャッシュがない初回ビルドでも14分かかるのは、直感的におかしいと感じました。

普段の開発で go run を実行したとき、そこまで遅いとは感じないからです。

4.2. 計測してみる

「推測するな、計測せよ」の精神で、ローカル環境でビルド時間を計測しました。

まず、最も大きいWeb API serverのみをビルドしてみます。

$ go clean -cache
$ time go build
go build 65.29s

約65秒。妥当な結果です。

次に、CI/CDと同様に全てのコマンド(Web API server + 複数のバッチ処理)をビルドするスクリプトを実行します。

$ go clean -cache
$ time ./build_all.sh
./build_all.sh 265.91s

約266秒(4分半)。 Web API server単体が65秒なのに、全体ビルドでは200秒も増えています。

4.3. リポジトリ構成と疑問

miiveのリポジトリは、Web API server、複数のバッチコマンドが共通の domain / model パッケージに依存する構成です。バッチ系のコードは全体の10%未満であり、しかも共通パッケージはキャッシュによりビルドがスキップされるはずです。

であれば、全体ビルドの時間は Web API server + α 程度に収まるはず。+200秒は明らかに異常です。

4.4. 原因の特定

go build -v オプションを使うと、コンパイル中のパッケージ名が表示されます。これを全コマンドのビルドで実行したところ、同じパッケージが何度もコンパイルされていることが判明しました。

$ go clean -cache
$ ./build_all_verbose.sh
...
math
math
math
...

原因はビルドスクリプトにありました。

ビルドスクリプト内の xargsgo build並列実行-P 0)していたのです。複数の go build プロセスが同時に走ると、最初のビルドがキャッシュを書き終わる前に次のビルドが始まるため、ビルドキャッシュが効かず、同じパッケージを何度もコンパイルしていました

4.5. 修正

修正は非常にシンプルです。xargs-P 0(無制限並列)オプションを外して直列実行にするだけでした。

直列でビルドすることで、最初の go build(Web API server)で生成されたキャッシュが後続のバッチコマンドのビルドで活用されるようになります。

$ go clean -cache
$ time ./build_all.sh
./build_all.sh 77.50s

266秒から78秒へ、約70%の短縮です。

5. Unit Testの改善

ビルドに加えて、Unit Testの改善にも取り組みました。

最も時間がかかっていたのはDB接続を伴うテストです。テストごとにDBのセットアップ・クリーンアップが走っており、これがボトルネックになっていました。

DATA-DOG/go-txdb を導入することで、各テストをトランザクション内で実行し、テスト終了時にロールバックする方式に変更しました。これにより、Unit Testは10分から4分30秒に短縮できました。

6. 改善結果

7. まとめ

今回のCI/CD改善で得られた教訓をまとめます。

  • 原因はシンプルな初期設定時のミスだった - go build を並列実行していたことでビルドキャッシュが無効化されていました。高度な最適化の前に、まず基本的な設定を見直すことが大切です。
  • 推測するな、計測せよ - 「遅い気がする」で終わらせず、実際に計測し、go build -v で挙動を確認することで原因を特定できました。
  • Goのビルドシステムは優秀 - パッケージ単位のビルドキャッシュが組み込まれており、正しく使えば非常に高速にビルドできます。

CI/CDの速度は、開発者の生産性とプロダクトの品質に直結します。「少し遅いけど、まあいいか」と放置していると、気づかないうちに25分になっていることもあります。

皆さんのCI/CDも、一度見直してみませんか?

We are hiring!

miiveではバックエンドでGoを使っている他にも、フロントではReact、モバイルではReact Nativeを採用しています。 この辺が好き!得意!という方で、決済に興味あるという方は、カジュアル面談のお申し込みをお願いします!

また、軽食を食べたりボードゲームをしながらmiiveについて知ってもらうmiive barを定期的に開催しています! ご参加お待ちしております 🥂 miive.notion.site

hrmos.co

https://careers.miive.jp