はじめに
こんにちは、delyでサーバサイドエンジニアをやっている山野井といいます。
kurashiruではサーバーサイドにRailsを使用しておりテストはRspecで書かれています。
CIはgithubリポジトリへのpushをフックしてAWS CodeBuild上でテストを走らせています。
またCI上のテストはparallel_tests gemを利用した並列化を行っていて、8プロセスで動いています。
弊社ではプロダクトの品質を保つ為、CIに通らないとデプロイできないルールを設けていまして、CIが完了するまでに時間がかかるとその分デプロイまでの時間もかかってしまうので1分でも早めたい気持ちがあります。
今回はアプリケーションコードには手を加えず、AWS CodeBuild上のCIの実行時間を少しづつ改善している話をしたいと思います。
実践
まずはCIの実行時間を改善する前にどこに時間がかかっているのかを把握する必要があります。
AWS CodeBuildには以下の11項目からなるphasesという概念があります。
SUBMITTED QUEUED PROVISIONING DOWNLOAD_SOURCE INSTALL PRE_BUILD BUILD POST_BUILD UPLOAD_ARTIFACTS FINALIZING COMPLETED
CodeBuildが実行されると各phaseが順番に実行されるようになっています。
この中で INSTALL, PRE_BUILD, BUILD, POST_BUILDのphaseはプロジェクトのルートに置いた buildspec.ymlに独自に処理を書くことができます。
以下がその例です。
version: 0.2
phases:
install:
commands:
- service mysqld start
pre_build:
commands:
- bundle install
- bundle exec rake parallel:create
- bundle exec rake parallel:migrate
build:
commands:
- bundle exec parallel_rspec spec
AWS CodeBuildでは以下の画像のようにAWSのコンソールから各phaseどれだけの時間がかかっているのかを見ることができるので、それを見ながら改善策を考えていきます。

弊社では PRE_BUILDフェーズで bundle install やdbの作成、migration処理を行っているのですが、ここに10分ほど時間がかかっていました。
よく見ると処理時間のほとんどがmigrationにかかっていることがわかりました。
bundle exec rake parallel:migrateコマンドはcpuのコア数分プロセスを立ち上げコマンドを実行するため、migration処理が8プロセスで実行されていました。
これを1つのDBのみmigrationを行い、migration後のダンプを取得し残りのデータベースに流し込む処理に変えることで10分ほど短縮することができました。
pre_build:
commands:
- bundle exec rake parallel:create
- bundle exec rake parallel:migrate[1]
- mysqldump -u root -prootpassword test > dump.sql
- bundle exec parallel_test -e 'mysql -usomeuser -psomepassword test$TEST_ENV_NUMBER < dump.sql'
Before

After

またテーブルに変更が無い限りは前回のDBの状態を用いても問題ないため、db/migrateのgitのhash値からキャッシュキーを生成し、ダンプそのものをキャッシュすることで
初回のmigration処理自体をスキップすることもできます。
pre_build:
commands:
- git log --pretty=format:'%H' -n 1 -- db/migrate > ~/mysql-dump-checksum
- mkdir -p ./.mysql-dump
- |
if [ -e "./.mysql-dump/$(cat ~/mysql-dump-checksum).sql" ]; then
bundle exec parallel_test -e 'mysql -usomeuser -psomepassword test$TEST_ENV_NUMBER < "./.mysql-dump/$(cat ~/mysql-dump-checksum).sql"'
fi
次に BUILDフェーズですが
BUILDフェーズでは主にRspecの実行を行っています。
今回はアプリケーションコードに手を加えない方法で高速化することを考えていたので特に変更は加えていません。
Rspec自体の速度を改善するには、一般的に遅いテストの洗い出しや、無駄なレコードの作成を行わないこと、無駄な通信を行わないことが挙げられます。
kurashiruでもここの最適化はさほど行っておらず、改善の余地があるので適宜改善していきたいと思っています。
最後に POST_BUILDフェーズですが、ここも5, 6分ほど時間がかかっていました。
AWSのコンソールからビルドログをよく見るとCodeBuildのキャッシュ機能によるs3へのアップロードに時間がかかっていました。
まだ設定の変更を完全に取り込むことはできていないのですが、CodeBuildのキャッシュタイプをs3から最近使用できるようになったローカルキャッシュに変更することで速度改善を試みています。
CodeBuildのローカルキャッシュは今年から利用できるようになった機能で、今まではキャッシュした結果をs3へアップロードしていたものをビルドホスト内に保持することができる機能です。
aws.amazon.com
この機能を有効にすることでs3への通信コストがかからなくなるため、ここにかかる時間を0にすることが確認できています。
最後に
1つ1つが地味な改善ですが、デプロイまでの時間を短縮でき、従量課金にかかる費用も節約できるので今後も時間を見つけて改善していきたいと思います。
最後までお付き合いありがとうございました。