pockestrap

Programmer's memo

CircleCIでparallel_testsのruntime logを保存する

TL;DR

# from .circleci/config.yml

- restore_cache:
    name: 'parallel spec runtime log'
    keys:
      - parallel-spec-runtime-log-{{ .Environment.COMMON_CACHE_KEY }}-{{ .Branch }}
      - parallel-spec-runtime-log-{{ .Environment.COMMON_CACHE_KEY }}-
- run:
    name: rake parallel:spec
    command: bundle exec rake parallel:spec
- save_cache:
    name: 'parallel spec runtime log'
    key: parallel-spec-runtime-log-{{ .Environment.COMMON_CACHE_KEY }}-{{ .Branch }}
    paths:
      - tmp/parallel_runtime_rspec.log
# from .rspec_parallel

--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log

目的

parallel_testsにはruntime logという仕組みがあります。 これを使うと、各specにかかった時間がログに記録されます。そして次回実行時にそのログを参照することで、各プロセスになるべく実行時間が均等になるようにspecを割り振ってくれます。 つまり、1つのプロセスに重いspecが集中して実行時間がそこに引きづられてしまうのを回避できます。

Test groups are often not balanced and will run for different times, making everything wait for the slowest group. Use these loggers to record test runtime and then use the recorded runtime to balance test groups more evenly.

https://github.com/grosser/parallel_tests/tree/v3.3.0#even-test-group-run-times

今回はこのruntime logをCircleCIにキャッシュとして保存しビルド間でログを共有するようにしました。 これにより各プロセスごとの実行時間の揺れを抑えることを狙っています。

設定の解説

簡単に設定を解説します。

まずはcircleci/config.ymlの説明をします。

# from .circleci/config.yml (再掲)

- restore_cache:
    name: 'parallel spec runtime log'
    keys:
      - parallel-spec-runtime-log-{{ .Environment.COMMON_CACHE_KEY }}-{{ .Branch }}
      - parallel-spec-runtime-log-{{ .Environment.COMMON_CACHE_KEY }}-
- run:
    name: rake parallel:spec
    command: bundle exec rake parallel:spec
- save_cache:
    name: 'parallel spec runtime log'
    key: parallel-spec-runtime-log-{{ .Environment.COMMON_CACHE_KEY }}-{{ .Branch }}
    paths:
      - tmp/parallel_runtime_rspec.log

restore_cacheしてsave_cacheしています。

なお、CircleCIのキャッシュは15日間でexpireします。

Caches created via the save_cache step are stored for up to 15 days.

https://circleci.com/docs/2.0/caching/#cache-expiration

裏を返せば15日間は古いキャッシュが使われるということです。 とはいえ各specの実行時間が大きく変わることは稀だと思われるので、15日間古いキャッシュを使ってしまう(最適な割り振りが行われない)ことは大きな問題ではないと考えています。 またキャッシュのキーには.Branchも含めているため、各Pull Requestの2回目以降のビルドでは最適な割り振りが行われます。

つぎに.rspec_parallelの設定を説明します。

# from .rspec_parallel (再掲)

--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log

といっても、これはparallel_tests gemのREADMEにある設定をそのまま持ってきています。 これを.rspec_parallelに書くことで、runtime logが指定したパスに出力されます。

適用結果

いくつかのテストの実行結果を見てみると、今まで1~2分ほどあった各プロセスの実行時間のばらつきが、数十秒程度まで抑えられたように感じています。 ただまともにプロファイリングをしていないため、本当に効果が出ているのか検証はできていません。