GitLab CIでのRailsのテスト(rspec) を高速化する。

rubyのテストを高速化する方法として、 parallel_tests を用いてテストを並列実行するというのがあります。

GitLab CI Runner を利用して、並列度を上げよう。というのが今回の試み。

まず、 parallel_tests で一般的な設定をしておきます。

config/database.yml で database name を被らないように

test:
  database: yourproject_test<%= ENV['TEST_ENV_NUMBER'] %>

次に、db:migrate もそれらすべてのDBで行うよう設定しておきます。

この時点で

parallel_rspec -n X 

とテストを開始すると、 X個のプロセスでテストが並列実行されます。

X個のプロセスでテストが並列実行されるということは、それだけメモリも消費しますし、CPUも消費します。なので、1ノードでの並列実行にはおのずと限界が訪れます。

そこで、 CI ランナーを複数つかって、さらに並列度を上げることを考えます。

GitLab CI には parallel という、テストを並列実行する仕組みがあります。

これを利用し、

test:
  parallel: 3

などと実行すると、3つが並列実行され得ます。 この際、各ジョブには CI_NODE_TOTAL CI_NODE_INDEX という環境変数がセットされます。TOTALのほうは全job共通で3、INDEXの方は、ジョブごとに1,2,3という値が設定されます。

これをもとに parallel_tests で分割した特定の部分のテストを実行するよう、設定します。

parallel_tests -n $(expr $CI_NODE_TOTAL "*" 2)  --only-group $CI_NODE_INDEX,$(expr $CI_NODE_INDEX + $CI_NODE_TOTAL )

などのように。

こうすると、 parallel: 3 と設定してあればテスト全体が 6分割され、 job1 では 1,4 job2では2,5 job3では 3,6 の部分が実行されます。

これで、ノードに空きがあれば、3ノード並列でテストが実行されることになります。

実行時間に偏りがある場合は、 RuntimeLogger でspecファイルごとの実行時間を計測したファイル(runtime-log)を作成し、--group-by time --runtime-log <runtime-log-file> とすると、ログファイルの時間的にできるだけ均等になるようにテストを分割してくれます。(この際、全jobで同じruntime-logファイルを利用しないと、jobごとに異なる分割方法で分割され、未実施のテストができてしまうので注意)

ただ、 parallel_tests では、分割の単位が spec ファイル単位なので、 特定のspecファイルでテスト全体の大半の時間を使う、ようなケースでは、そのspecファイルを処理するのにかかる時間より短くなることはありません。時間のかかるspecファイルがある場合は、最悪、ファイル自体を分割することも考えるべきでしょう。