GitLab CI/CDの深淵:キャッシュとアーティファクトの「完全掌握」によるパイプライン極限最適化
多くのエンジニアがGitLab CI/CDの速度に不満を漏らす。その原因のほとんどは、`cache`と`artifacts`という二つの概念を「なんとなく」使い分けていることに起因する。
この二つは、単なる一時保存場所ではない。「パイプラインのライフサイクルを制御するメモリ管理層」と捉えるべきだ。この設計思想を理解せずして、ビルド時間を削り出すことは不可能だ。
—
1. 概念的対立:キャッシュは「利便性」、アーティファクトは「生存権」
まず、心に刻んでほしい。
- Cache (キャッシュ): ビルドの「加速」に使うもの。失われても再構築可能だが、あればビルドが速くなる(例: `node_modules`, `~/.m2`)。
- Artifacts (アーティファクト): ビルドの「成果物」そのもの。ステージをまたぐために必須であり、これがないとパイプラインは分断される(例: バイナリ, コンパイル済みのJS, テストレポート)。
なぜ混同が地獄を招くのか
キャッシュを「成果物の受け渡し」に使おうとする輩がいるが、これは自殺行為だ。Runnerのポリシーやディスク容量の圧迫により、キャッシュは予告なく消える。「キャッシュが消えてもビルドが成功する」状態こそが正常な設計である。
—
2. キャッシュの極限最適化:分散型Runnerでの落とし穴
分散環境(Kubernetes Executor等)において、キャッシュは分散S3ストレージへアップロード・ダウンロードされる。このI/Oオーバーヘッドを無視してはならない。
`cache:policy`の使い分けで無駄なI/Oを削る
デフォルトの`pull-push`は、毎回キャッシュをアップロードし直すため遅い。読み取り専用のジョブには必ず`pull`を指定せよ。
.gitlab-ci.yml
.node_cache: &node_cache
cache:
key: ${CI_COMMIT_REF_SLUG} # ブランチごとにキャッシュを分離
paths:
- node_modules/
policy: pull # デフォルトはpull-push。並列ジョブではpullのみに制限する
test:job:
<<: node_cache
policy: pull
script:
- npm test
キャッシュの「キー戦略」をハックする
`key: ${CI_COMMIT_REF_SLUG}` は汎用的だが、依存関係が変わらない限り再構築は不要だ。
`key: files: – package-lock.json` とすることで、依存関係のハッシュ値が変化した時のみキャッシュを更新させることが可能だ。これにより、無駄なアップロードを劇的に減らせる。
—
3. アーティファクトの「生存戦略」:パイプラインの寿命を制御する
アーティファクトは、ジョブ終了時にRunnerからGitLabのバックエンドへ転送される。巨大なファイルを無計画に渡すと、ネットワークI/Oだけで数分溶ける。
「最小限の転送」と「ライフサイクル」の自動化
不要な成果物を保持し続けるのはストレージの浪費であり、クリーンアップ処理の遅延を招く。
build:app:
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour # 必要以上に保持しない。後続ステージに渡せればそれでいい
exclude:
- dist//.map # デバッグ用ソースマップが不要なら除外して転送量を減らす
—
4. 上級者向けのハック:GitLab APIによるパイプライン・チューニング
パイプラインの実行速度を追求するなら、UIに頼るな。GitLab APIを叩き、アーティファクトの消費状況やキャッシュのヒット率を監視・解析するスクリプトをCI環境に組み込むのだ。
キャッシュのクリアと最適化スクリプト
Runnerのディスクが肥大化した際、手動削除は愚策だ。以下のAPIを活用し、古いキャッシュを自動削除するパイプラインを別に用意せよ。
特定のプロジェクトの古いキャッシュをAPI経由でパージする例
curl –request DELETE \
–header “PRIVATE-TOKEN: $GITLAB_TOKEN” \
“https://gitlab.example.com/api/v4/projects/$CI_PROJECT_ID/registry/repositories”
—
5. 伝説的エンジニアからの提言
極限まで高速化したいなら、以下のチェックリストを叩き込め。
1. キャッシュは「ローカル・ディスク」を信じるな: Runnerのローカルキャッシュは再利用性が低い。S3/GCS等の分散ストレージをバックエンドにし、リージョンを一致させること。
2. アーティファクトは「圧縮」を疑え: `artifacts`はデフォルトでgzipされる。巨大なバイナリであれば、圧縮時間を考慮し、`untracked: true`の設定を見直す必要がある。
3. `dependencies`を明示せよ: デフォルトですべてのアーティファクトを引き継ぐと、前のステージの巨大な成果物までダウンロードすることになる。必要なジョブのみ指定せよ。
test:
stage: test
dependencies:
- build # ビルドステージの成果物のみを明示的に取得する
script:
- ./run_tests.sh
最後に
CI/CDとは「自動化の自動化」である。キャッシュのヒット率が低い?なぜだ?なぜ依存関係が頻繁に変わる?アーティファクトが重すぎる?なぜモジュールが分離されていない?
パイプラインの遅さは、開発の設計の遅さを可視化しているに過ぎない。
このツールを骨の髄まで使いこなすことは、自らのアーキテクチャを再構築することと同義だ。さあ、今すぐ無駄な秒数を削り出せ。エンジニアの時間は、それほどまでに貴重なのだから。