🎉

【Github Actions】ci_index/ci_total & parallel_rspecを使ったCI高速化【Rails】

2023/09/15に公開

RSpecのCIを早くできないんじゃ・・
そんな同志にこの記事を捧ぐ・・😌

TL;DR

  • GitHub Actionsのci_index/ci_totalとparallel_rspecを組み合わせたらCIでのRSpec実行時間を短くできた

ことの経緯

業務において私を含めた開発メンバーが最も触るRailsリポジトリがあるのだが、rspecをparallel_testによって2並列で実行しても20分程度かかるという状況だった。

とりあえず単純にparallel_testの並列数を3、4と上げてみたが実行時間は18分と、大した改善にならない・・。

そこで色々調べて、GitHub Actionsのci_index/ci_total と parallel_rspecの組み合わせを試してみると(cacheがヒットした時で)3分半程度まで短縮できたので、この記事でシェアしようと思う。

ci_index/ci_totalの指定で何が起こるのか

ci_index および ci_total は、GitHub Actionワークフローにおいてjobs.{job_name}.strategy.matrix下で指定できる項目だ。

この2項目を設定すると、そのjobを実行するインスタンスが ci_total の数だけ立ち上がって並列実行される。

ci_index は、そのインスタンスそれぞれに割り振られるIDのようなイメージだ。

しかし、この2項目の指定だけでは並列実行されるインスタンスそれぞれで同じテストが重複して実行されてしまう。それぞれが重複ない形で、かつ全インスタンスでみると全てのテストが実行されるようにするにはもうひと工夫が必要だ。

parallel_rspecの--only-groupオプション

上で述べたような「もうひと工夫」はparallel_testを使ったrspecの実行の場合、--only-groupオプションの指定によって実現可能だ。

--only-groupオプションは、「(テスト全体をn分割した内の) m番目のグループの
テストのみを実行する」ように指示するもので、このグループ番号 m に実行インスタンスごとに値が異なるci_indexの値をそれぞれ指定することで、実質的に全テストの分散実行をすることができる。

実装のサンプル

以上の点を踏まえると、実装のサンプルとしては以下のようになる。

name: RSpec CI
on: pull_request
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      # あるインスタンスでテストが失敗し停止した際に
      # 他のインスタンスが停止しないようfail-fastをfalseにする
      fail-fast: false
      matrix:
        ci_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
        ci_total: [16]
    steps:
      - uses: actions/checkout@v4
      #
      # ... (setup-rubyなどあれこれ)
      #
      - name: RSpec
        env:
          CONCURRENCY_INDEX: ${{ matrix.ci_index }}
          PARALLEL_TESTS_CONCURRENCY: ${{ matrix.ci_total }}
        run: |
          bundle exec parallel_rspec \
          -n $PARALLEL_TESTS_CONCURRENCY \
          --only-group $CONCURRENCY_INDEX

参考

https://www.testmo.com/guides/github-actions-parallel-testing
https://www.m3tech.blog/entry/reduce-rspec-execution-time-on-gitlab-ci

Discussion