CI上のRailsのRSpecテストが30分もかかっていてストレスフルだったので、テストを並列化する等で30分かかっていた時間を12分まで削減しました。
そのときのメモ。
環境
- AWS CodeBuild
- Ruby
- Rails
- Parallel_tests
- MySQL
前提
docker compose
でテストは閉じている。そのため、RDS等のアクセスは発生していない。
対応
parallel_testsライブラリを入れる
並列化ライブラリのparallel_tests
を導入します。
group :development, :test do
gem 'parallel_tests'
end
テストのDB名を修正する
並列化したプロセスごとに独立したスキーマを割り当てないとデッドロックが頻発するのでプロセスごとにスキーマを作成します。
parallel_tests
ライブラリが実行しているプロセスごとに環境変数TEST_ENV_NUMBER
を用意してくれるので、suffix
に環境変数を指定します。
test:
database: sample<%= ENV.fetch('TEST_ENV_NUMBER', '') %>
テスト実行時のRakeを自作する
私の環境の場合docker compose
を使用して閉じた環境でテストしていることもあり、商用環境テーブルは不要でした。そのため、parallel_tests
のライブラリをそのまま使用するとマイグレーションチェックが走ってエラーが発生してしまったため、その過程を飛ばしています。
また、自作Rakeがdevelop test
groupに依存しているのもあり、商用でエラーになってしまうので、LoadError
をスキップするようにしています。
NOTE
begin
require 'parallel_tests/tasks'
rescue LoadError
puts 'Unable to load parallel_tests/tasks, skipping'
end
namespace :my_parallel do
NOTE
task :spec, [:count, :pattern, :options, :pass_through] do |_t, args|
ParallelTests::Tasks.load_lib
command = ParallelTests::Tasks.build_run_command("spec", args)
abort unless system(*command)
end
end
CIを修正する(buildspec-ci.yml)
AWS CodeBuild
を使用しているので、次のようなyml
を用意しました。
phases:
pre_build:
commands:
# MySQL 8 だと_utf8mb4 というsuffixがついてしまうので、それを削除する
- sed -i 's#_utf8mb4\\\\'\''_\\\\'\''#'\''_'\''#g' db/schema.rb
# スキーマの削除・作成・db/schema.rbの反映
- docker-compose run --rm api rails parallel:drop parallel:create parallel:load_schema
build:
commands:
# 自作rakeでのテスト実行
- docker-compose run --rm api rails my_parallel:spec
プロセス数は指定したイメージのCPU数に依存するので、もし指定したCPU数よりも少ないプロセスで動かしたい場合は環境変数で指定してください。ローカルPCで挙動確認すると、20並列で動いてしまって期待の速度が出なかったので制御していました。
env:
variables:
PARALLEL_TEST_PROCESSORS: 4
結果
もともと、30分の内訳としては次のとおりでした。
テストを並列化した結果、25分になり、内訳は次のとおりでした。
ここまではコンピューティングが3GBメモリ2vCPUでしたが、7GBメモリ4vCPUに変更した結果。16分になり、内訳は次のとおりでした。
最後に15GBメモリ8vCPUまで性能を上げて、12分になり、内訳は次のとおりでした。
AWSが用意するコンピューティングとのバランスを考えたときに、並列化によるこれ以上のメリットはないと考えてここで終了しています。
ソースコード
なし
終わりに
イメージ作成したり、DBを用意したりするPRE_BUILD
が測定前に想像していなかったほど重く、ここを改善しないとこれ以上の高速化は見込めませんでした。
ビルドをDocker Build Cloud
に任せたり、DBを作ったイメージごとsave
して流用する等々の考えもありましたが…。複雑になる上、高速化に貢献するか微妙なラインだったので、検証はしていません。
EC2だけでなく、Lambda
をうまく組み合わせたら早いとかそういう話があればぜひ聞きたいです。
参考情報
https://www.publickey1.jp/blog/24/docker40docker_build_cloudappleaws_graviton.html