dely engineering blog

レシピ動画サービス「kurashiru」を運営するdelyのテックブログ

RailsのCIにかかる時間を少しづつ改善している話

はじめに

こんにちは、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どれだけの時間がかかっているのかを見ることができるので、それを見ながら改善策を考えていきます。

f:id:yamanoi-y:20190627143731p:plain


弊社では 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
f:id:yamanoi-y:20190627145817p:plain
After
f:id:yamanoi-y:20190627145920p:plain


またテーブルに変更が無い限りは前回の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つが地味な改善ですが、デプロイまでの時間を短縮でき、従量課金にかかる費用も節約できるので今後も時間を見つけて改善していきたいと思います。
最後までお付き合いありがとうございました。