GitLab CI ジョブの高速化 (キャッシュを使わない)
GitLab CI ジョブの高速化 (キャッシュを使わない)
こんにちは。日夜 GitLab CI ジョブの実行時間の短縮と戦ってます。
基本的に、継続的インテグレーションは処理時間が短ければ短いほど効果的になります。プッシュしてから30分後にテストが失敗してるよって CI から教えてもらっても、その時にはすでに別のタスクに集中してて、今のタスクを中断してまでそのテストの失敗の解消から先にやるよっていう人は少ないのではないでしょうか?
10 秒とかで終わってくれればいいんですけどねぇ。実際には難しいので 5 分程度を目安にして日夜戦っているわけです。
仕事よりそっちに一生懸命だったり? (笑)
GitLab CI キャッシュってそんなに有効ではない
ジョブの高速化のテクニックの一つとして、npm install
後の node_modules
をキャッシュしておいて、ほかのジョブでも使いまわすというものがあります。
もちろんそれなりの効果はあるのですが、それなりです。
最大の理由はGitLab RunnerのZIPアーカイバーが遅いこと。node_modules
が大きなサイズだと、このキャッシュの圧縮・展開・アップロード・ダウンロードだけでかなりの時間を食います。
GitLab.comのSaaS版Runnerはクラウドでかなりネットワーク帯域が太いので、キャッシュしない方が速いじゃんか! みたいな状態になることも。
まあ、ほかの人の迷惑になるのでキャッシュするのですが。
最近の GitLab Runner は FF_USE_FASTZIP
Feature Flags に 1
を設定するとより高速なZIPアーカイバーの fastzip を使ってくれます。GitLab CI キャッシュを使うときはこのフラグを必ず ON にしてください。
詳しくはGitLab CIのキャッシュ圧縮展開をFF_USE_FASTZIPで爆速にするを読んでください。本当にひゃっほーい! って感じなので。
GitLab CI キャッシュを使わないという方向転換
はじめに言っておきますが、GitLab.com の SaaS 版 Runner ではまったく使えないテクニックです。
Docker executor な Runner では、ジョブの実行ごとにコンテナーを起動しているため、ジョブの作業ディレクトリはジョブの完了と同時に破棄されると思われがちですが実は違います。ジョブの作業ディレクトリは Docker Volume に割り当てられていて、ジョブの完了後の残ります。次にジョブが実行されるとこの Docker Volume は再利用されます。
node_modules
は前回のジョブの結果が残っているわけですね。つまり、キャッシュする必要はないのです。
と思ったら、ジョブの開始前に node_modules
は削除されます。
ジョブの実行環境が開始されてから次のようなフローで処理されます。
-
git fetch
orgit clone
git clean -ffdx
- extract cache
- extract artifact
- ...
この 2 の git clean -ffdx
で node_modules
を含む Git リポジトリにないファイルはすべて削除されます。残念です。
ドキュメントを探すと、GIT_CLEAN_FLAGS
フラグで git clean
の引数を変えられることがわかります。
variables:
GIT_CLEAN_FLAGS: -ffdx -e node_modules/
とすると、node_modules
ディレクトリはめでたく削除されず、GitLab CI キャッシュを使わなくても前回のジョブのものをそのまま利用することができます。
キャッシュの圧縮・展開にかかる時間をスキップできるのでめちゃくちゃ速くなります。
最後に
GitLab CI キャッシュ遅すぎる! ムキーっ! ってなって、俺が tar archiver の機能を突っ込んでやると GitLab Runner のコードを必死に追いかけたのがきっかけです。
あれ? なんか FF_USE_FASTZIP
で切り替わるぞ? なにこれ爆速やん!
あれ? あれ? って感じでだいぶ思ってたのと違ったという。
残念ながら、SaaS 版 Runner はジョブごとに新規の仮想マシンを割り当ててその中の Docker Engine を利用していて、ジョブの完了後はすべて破棄されているため、この方法は使えません。
SaaS 版 Runner はそんなに高速ではないので、適当なマシンを Runner にして、この方法で高速化するだけで、分単位で速くなることもあります。
Discussion