君はCircleCIを使い倒しているか!CircleCIの実行時間を半分にした話
こんにちは
noteでArchitectureチームに所属しています、GENDOSUです。
CircleCI遅い!!(突然)
というのも
noteでは今までnoteではCircleCIが遅いという課題があり問題があって、CircleCIが遅くなっていました。しかし、〇〇したことで、解決することができました。今回はその方法について書いていきたいと思います。
noteの構成
まず、単に遅いと言っても改善できるのかどうか調べないといけないので
現状と環境の把握をしていきます。
簡単にnoteの構成を説明すると以下のようになります。
フロントエンド
Vue / Nuxt
React / Next(一部)
サーバサイド
Ruby on Rails
テストRSpec
CIツール
CircleCI
CircleCIはサーバサイドに限らずテスト自動化の部分で使われてます。
今回「遅い!」と言っているのはサーバサイドが対象です。
CircleCIが遅い原因の調査
では、ネックになっている部分の調査です。
noteのサーバサイドは現在モノリシックな構成で
全体のテストを実行すると
CircleCIのランナー6本並列で実行して14分ほどかかっているのが図から分かると思います。
CircleCIではテストを実行するインスタンスをランナーと呼んでおり
このランナーのスペックは設定ファイルの中で指定することが出来ます。
noteで現在使っているスペックはmedium+(CPU: 3、メモリ: 8GBのインスタンス)を指定しています。
しかし、CPUは3つ使えるはずなのに、CPU usageのグラフを見ると1/3だけしか使われていないことが分かります。
実際、RSpecはそのまま起動すると1プロセスでの実行になるので、ほぼ解釈として間違えていないはず。
CPUの使用率100%を実現するためにランナー内で並列処理をさせる
で、理想は・・・
現在のCPUが1/3しか使われていない状態から、CPUの使用率100%という状態に持って行きたい。
そこで・・・
ランナーの中で並列処理をさせようと考えました。
noteのCircleCIのランナーを再確認
CircleCIのランナーを6本立てて並列実行
ランナーの中でRSpecテストが1本動いている
合計6本の並列性
これを以下のようにカイゼンしてみます。
CircleCIのランナーを6本立てて並列実行(ここは変わらず)
CircleCIのランナーを6本立てる
ランナーの中でコア数分テストを並列実行
合計24本の並列性
CircleCIで並列化するための実装の仕様を確認します。
並列で動かすということで、データベースも並列数分必要になり、実行結果も並列数分作成されます。
CIrcleCIから見た場合、現状と同等のテスト結果が受け取れる必要があります。
並列処理の実装をしてみる
ということで、実際にやってみる。
parallel_testを使用
並列実行には、parallel_testsを使用
これは、コア数分RSpecをパラレルに実行してくれるものです。
DBの並列化
データベースを並列数分作成するのは
parallel_testsのコマンドで実行出来る
並列のそれぞれで、違うデータベースを参照するのは、parallel_testsが実行時環境変数を作ってくれていて個々のプロセス事にプロセス番号が入るので
それをもとにテーブルを切り替える
出力ファイルの並列化
実行結果のファイルもデータベース同様環境変数を使ってプロセス事に分けて出力するようにする
上記を実行した結果
どれだけ早くなった?
並列処理を疾走した結果、実行時間が14分から6分と、半分以下になりました。
計算上はもっと早くなるはず・・
倍速にはなったのですが、もう少しはやくなる想定でした。
並列性が4倍なら1/4になるのでは??
単純計算だと、 1/4になるのですが、ランナーの中では・・・
実行イメージの取得&展開
キャッシュ展開
テーブル作成
などなど、これらの実行は並列になりません。
加えて、ランナーのIOなどの性能の上限というのも考慮しないといけないので、単純に1/4にはなりにくいです。
並列実行は出来たが
性能すべてを使い切れてはいない
が、それでも一番
時間がかかるフェーズが
並列実行出来ているので
よしとする。
やったことおさらい
CircleCIのランナーが6並列なのをここの設定はいじらず
ランナーの中をさらに並列にする
データベースを並列数分作成
CircleCIのランナーのリソースタイプをmedium+からlargeに変更
プロセスが増える分メモリが消費されるため
その他、最適化した内容
Dockerイメージにあらかじめ必要なパッケージをインストール
apt install [パッケージ]
Dockerイメージはus-east-1に配置
プロジェクトのチェックアウトで出来る.gitディレクトリをキャッシュ
assets:precompileはRSpec実行の前に事前に実行
コスト計算(単純計算)
resource_typeを一つあげた割にはコスト削減になっている(はず)
CircleCIの速度改善に取り組んでみて
今回、CircleCIの速度改善という対応をして見て
CircleCIのドキュメントにある通りのparallelismという並列化をする設定だけでは無く、ランナーのスペックに応じた対応をすることで、飛躍的に高速化が出来るということが分かりました。
また、CircleCIによるテスト実行が高速化されることで
DevOps のトップレベルパフォーマンス指標といわれる4つの指標の中の3つ
リードタイム
デプロイの頻度
平均修復時間
が改善されそうです。
(DevOpsに関する指標についてはまだ取れていないので今後の課題です。)
最後に
noteではいろんな人を募集してます。興味ある方はぜひぜひ来てください!