CIビルド時間の改善
2020年2月18日 Ethan Dennis (@erdennis13)、João Moreno (@joaomoreno) 記
Visual Studio Codeは、多くの可動部分とアクティブな参加者リストを持つ大規模なプロジェクトです。私たちは、ビルドと継続的インテグレーションのインフラストラクチャを維持することで、優れたエンジニアリングプラクティスを維持するためにAzure Pipelinesを積極的に使用する方法を紹介しました。このブログ記事では、Azure Pipelines Artifact Caching Tasksを使用してCIビルド時間を劇的に短縮した方法について説明します。
私たちは、以前のブログ記事で、CIビルド時間を33%短縮した方法について説明しました。これは、ビルド時にパッケージを解決するのではなく、VS Codeが消費するnodeモジュールをキャッシュするカスタムビルドタスクを使用することで達成されました。このパフォーマンス向上には満足していましたが、私たちが構築したキャッシュタスクをどこまで押し進めることができるかを確認したかったのです。
前回CIエンジニアリングについて話したとき、ターゲットプラットフォームはWindows、macOS、Linuxに及んでいました。今日現在、VS CodeはリモートサーバーコンポーネントのためにArm64やAlpine Linuxなど、より多様なプラットフォームをターゲットにしています。合計で8つの異なるターゲットがあり、すべてが共通のビルドステップを共有しています。この記事では、キャッシュタスクを活用してCIの重複を減らし、ビルド時間をさらに改善した方法を概説します。
改善の余地
では、すべてのビルドジョブに共通するステップとは具体的に何だったのでしょうか?各ビルドターゲットには、同様の一連のステップに従うジョブがあります。非常に高いレベルで、各ジョブは次のことを行う必要があります。
- 依存関係の復元
- TypeScriptとJavaScriptのLint
- TypeScriptからJavaScriptへのコンパイル
- ユニットテストスイートの実行
- 統合テストスイートの実行
- VS Codeのパッケージ化
私たちのキャッシュタスクは、依存関係の復元ステップを高速化するための明白な選択肢でした。たとえば、package-lock.json
ファイルがめったに変更されないことを考えると、なぜ高価なnpm install
ステップを実行するのでしょうか?以前の実行結果をキャッシュできるのに。以前にパッケージのキャッシュについて議論したので、この記事が興味深いのは、他のステップにキャッシュを適用した方法です。
Lintとコンパイルはプラットフォームに依存しないため、これらのステップは、すべてのエージェントがこの作業を繰り返し実行するのではなく、単一のビルドエージェントが実行し、その結果を他のプラットフォーム依存のエージェントと共有することで簡単に実行できました。私たちは、まさにこの責任を負うLinuxビルドエージェントを作成しました。つまり、パッケージを復元し、ソースコードをLintしてコンパイルすることです。私たちがしなければならなかったのは、結果を他のエージェントと共有することだけでした。
すべてをキャッシュする
ビルドエージェント間でキャッシュ結果を共有するために、プラットフォームに依存しないキャッシュが必要でしたが、これは当初キャッシュタスクではサポートされていませんでした。そこで、オプションのplatformIndependent
パラメーターがAzure Pipelines Artifact Caching Tasksに追加されました。
VS CodeがplatformIndependent
パラメーターを使用する方法は次のとおりです
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: keyfile
targetfolder: target
vstsFeed: $(ArtifactFeed)
platformIndependent: true
nodeモジュールをキャッシュする場合、package-lock.json
ファイルをキャッシュキーとして使用するのが論理的です。このファイルが変更された場合、キャッシュを無効にする必要があります。コンパイル出力をキャッシュする場合、コードベース全体がキャッシュキーとして機能する必要があります。物事を単純化するために、新しいコミットは必然的に新しいキャッシュエントリを作成するため、HEADコミットをキャッシュキーとして使用することにしました。これは、ビルドエージェント間で実行されるにもかかわらず、単一のビルドは常に単一のコミットに対して実行されるため、私たちの目的にはうまく機能します。
もう1つの欠けていた機能は、ビルドジョブごとに複数のキャッシュを作成する機能でした。私たちは今、2つのキャッシュ(nodeモジュール、コンパイル)を扱っており、各キャッシュを個別にアドレス指定する方法がありませんでした。キャッシュタスクはCacheRestored
という環境変数を出力し、これを使用してビルドタスクを楽観的にスキップできます。この環境変数は、単一のキャッシュと対話するビルドではうまく機能しますが、複数のキャッシュではそれほどうまく機能しませんでした。CacheRestored
がどのキャッシュを参照しているのか疑問に思いました。そこで再び、オプションのalias
パラメーターがAzure Pipelines Artifact Caching Tasksに追加されました。
そして、私たちがalias
パラメーターを使用する方法は次のとおりです
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: "yarn.lock"
targetfolder: "node_modules"
vstsFeed: "$(ArtifactFeed)"
alias: "Packages"
- script: |
yarn install
displayName: Install Dependencies
condition: ne(variables['CacheRestored-Packages'], 'true')
ここでは、Packages
のエイリアスが環境変数出力に追加され、単一のビルドジョブでNPMパッケージとコンパイル出力をキャッシュできるようになります。私たちはついに、CI作業の多くを重複排除し、一度だけ実行してプラットフォーム固有のエージェント間で共有できるようになりました。
特定のユースケース、つまりビルドの再送信を考えると、最後の最適化の余地がまだありました。テストが不安定であったり、一部のエージェントがランダムに失敗したりするため、以前にビルドされたコミットでVS Codeビルドを再トリガーする必要がある場合があります。理想的には、共有エージェントは共通のコードを復元または再コンパイルせず、プラットフォーム依存のエージェントに作業を実行させるべきです。私たちが気付いた問題は、コンパイルキャッシュパッケージが巨大で、それらを復元するのに約8分かかることでした。共有エージェントがそのキャッシュが存在する場合に単に制御を譲るだけなので、すべてが無駄になります。そこで、新しいオプションのdryRun
パラメーターが再びAzure Pipelines Artifact Caching Tasksに追加されました。これにより、キャッシュパッケージを復元せずにその存在を確認できます。これにより、ビルドの再送信から効果的に8分を削減できます。
ビルドでdryRun
パラメーターを使用すると、次のようになります
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: commit
targetfolder: output
vstsFeed: "$(ArtifactFeed)"
dryRun: true
- script: |
npm run compile install
displayName: Install Dependencies
condition: ne(variables['CacheExists'], 'true')
これにより、dryRun
パラメーターと連携して動作する新しいCacheExists
変数も導入されたことに注意してください。
結果
これらの変更が実装されると、総ビルド時間が大幅に削減されました。次の表は、VS Codeがターゲットとする各プラットフォームの総ビルド時間の変化を示しています。
プラットフォーム | 以前 | 以後 | 時間短縮 |
---|---|---|---|
Windows | 58分 | 44分 | 24% |
Windows 32 | 59分 | 46分 | 22% |
Linux | 38分 | 23分 | 39% |
macOS | 68分 | 42分 | 38% |
Linux Arm | 22分 | 21分 | 5% |
Linux Alpine | 23分 | 26分 | -13% |
Linux ArmとLinux Alpineターゲットは、VS Codeリモートサーバーコンポーネントのみをビルドするため、元のビルド時間は十分に良好でした。しかし、標準のVS Codeクライアントプラットフォームといくつかの共通タスクを共有するため、共通のビルドエージェントに依存させることにしました。これにより、あるケースではオーバーヘッドが増加したため、ビルド時間がわずかに増加しました。
ビルドの再送信は、共有エージェントのタスクを完全にスキップできるため、大幅に改善されました。たとえば、macOSのいくつかの数値は次のとおりです。
プラットフォーム | 以前 | 以後 | 時間短縮 |
---|---|---|---|
macOS | 68秒 | 34秒 | 50% |
合計で、VS CodeのCIビルド時間が約50%削減されたことを大変嬉しく思います!最良のニュースは、私たちのビルド定義からインスピレーションを得て、独自のビルド時間改善を実現できることです。
Happy Caching,
Ethan Dennis、Developer Services シニアソフトウェアエンジニア @erdennis13
João Moreno、VS Code シニアソフトウェアエンジニア @joaomoreno