LoginSignup
25
12

More than 1 year has passed since last update.

github actionsのjobを高速にするために取った対策

Last updated at Posted at 2022-06-16

はじめに

  • github actions で CICD パイプラインを構成して運用している
    元々プロジェクト内でテストコードが少なかったのもあり最初の頃は気にする程でもなかったのだが、
    テストコードを追加し品質を上げていく過程で気づけば github actions の pull Request 時に走るジョブの実行時間が 20m 弱かかるようになっていた。
    github actions の無料枠内での実行時間上限が近づいていたこともあり、パイプラインの高速化対応を行った。

やりたいこと

  • github actions のジョブ自体の実行時間を早くしたい。
  • github actions の実行時間無料枠を有効的に使いたい。

前提

  • React(CRA) + Typescript
  • テストフレームワークは Jest
  • npm を使っている

状況

プルリク時のジョブ実行時間抜粋

毎回大体 20 分弱かかっている
実行時間抜粋

パイプラインの実行時間内訳

  • 主に時間がかかっているステップは2つ
    • Run cd ./front-appという step でnpm installを行い、依存関係のあるライブラリを持ってきている
    • Testreact-scripts testを実行している。カバレッジファイルも出力する
        実行内訳

github actions 月末での消費量

作業ブランチへの push 時に毎回 20 分もテストを実行させているので、このジョブがプロジェクト全体の利用枠を圧迫していることは明白。
消費量

検討した対策

  • github actions 上のテストを並列化する
  • Jest からめちゃくちゃ早いと噂されている Vitest への移行
  • node_modulesのキャッシュ化
  • ブランチとの修正差分に影響ある部分だけテストを実行する

対策を実行

github actions 上のテストを並列化する

  • test の実行数を分割して、複数 job での並列実行させることでパイプラインの実行時間を短くする作戦。
  • 確かに早くはなるが、job 単位での分割ということは複数のruns-onを指定するということになる。
  • github actions の実行時間無料枠の消費は job 単位(runs-on単位)となるため、パイプラインとしての実行時間は短縮されるが無料枠の消費時間は変わらない。
  • 以上から、「github actions の実行時間無料枠を有効的に使いたい。」 が満たせないと判断したため、この対応は見送った。

Jest からめちゃくちゃ早いと噂されている Vitest への移行

  • 巷でテストの実行が早くなると噂されている Vitest を導入する作戦
  • 実際に 388 テストファイルを Jest->Vitest へ移行して、それぞれで同じテストを使って計測してみた
    ※ どちらも並列実行数 6 で検証

1件のテストをチョイスして単体でのスピード比較

  • Enzyme+snapshot React Component テストで比較

Jest

enzyme+jest

Vitest

enzyme+vitest

  • testing-library React Component テストで比較

Jest

testing-library+jest

Vitest

testing-library+vitest

  • API 接続部分の単純な Typescript ファイルのテストで比較

Jest

normal+jest

Vitest

normal+vitest

全件分テストを実行してスピード比較

Jest

jest

Vitest

vitest

結果

テスト項目 Jest Viest
Enzyme 17.44s 10.01s
testing-library 14.81s 6.00s
普通のテスト 7.613s 3.84s
全テスト 277.41s 451.89s
  • 少数の実行だと Vitest は Jest より早いことが確認できたが、全体で計測したところ Jest の方が早かった。なぜだ・・・
  • 結論としては、テスト総実行が Vitest だと遅くなってしまう要因があり、調査も必要だと判断したため導入を見送った

node_modulesのキャッシュ化

  • github actions の中で node_modules をキャッシュできるという記事があったので導入を行った
  • actions/setup-nodeだけでキャッシュ化ができるという記事が合ったが、キャッシュが効いていなかったのでactions/cacheを併用する

元の github actions step

- name: Setup
  uses: actions/setup-node@v2
  with:
    node-version: "16"
- run: |
    npm install

キャッシュ化導入後 github actions step

- name: Setup
  uses: actions/setup-node@v2
  id: setup_node_id
  with:
    node-version: "16"
- uses: actions/cache@v3
  id: node_modules_cache_id
  env:
    cache-name: cache-node-modules
  with:
    path: "**/node_modules"
    # package-lock.jsonのハッシュ値をキャッシュ用のkeyとして入れることで、依存関係が変わったらキャッシュを取り直すようになっている
    key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
- run: echo '${{ toJSON(steps.node_modules_cache_id.outputs) }}'
# キャッシュがあれば、npm install をスキップする
- if: ${{ steps.node_modules_cache_id.outputs.cache-hit != 'true' }}
  run: |
    npm install

結果

  • 2 回目の実行以降、npm installのインストール step が短縮されたことが確認できた
    cache-result

ブランチとの修正差分に影響ある部分だけテストを実行する

  • そもそもプルリク時にコードの 1 部しか修正していないのに、テストの全実行をしていることが非効率なのではないかと気づいた

  • よって、プルリク時のテストは作業ブランチと master との差分に影響するテストのみ実行するようにパイプラインを変える

  • Jest のコマンドに--changedSinceというオプションがある

    Runs tests related to the changes since the provided branch or commit hash.
    If the current branch has diverged from the given branch,
    then only changes made locally will be tested. Behaves similarly to --onlyChanged.

    指定したブランチあるいはコミットハッシュからの変更に関連するテストを実行します。
    現在のブランチが指定したブランチから分岐している場合は、ローカルで行われた変更のみがテストされます。

    • プロダクトコードを修正した場合・・・影響するテストが実行される
    • テストコードを修正した場合・・・修正したテストが実行される

before

  • github actions step

    - name: Checkout
      uses: actions/checkout@v3
    
    - name:
        Setup
        # 省略
    
    - name: Test
      run: |
        npm run test:ci
    
  • package.json

    {
      "scripts": {
        "test:ci": "CI=true TZ=Asia/Tokyo react-scripts test --watchAll=false"
      }
    }
    
  • 実行時間
    result-before

    • テスト全部実行なのでそれなりに重い

after

  • github actions step

    - name: Checkout
      uses: actions/checkout@v3
      with:
        # fetch-depth: 0 を設定すると、すべてのブランチとタグのすべての履歴がフェッチされます。
        fetch-depth: "0"
    
    - name:
        Setup
        # 省略
    
    - name: Test
      run: |
        npm run test:diff:ci
    
  • package.json

    {
      "scripts": {
        "test:ci": "CI=true TZ=Asia/Tokyo react-scripts test --watchAll=false",
        "test:diff:ci": "CI=true TZ=Asia/Tokyo react-scripts test --watchAll=false  --changedSince origin/master"
      }
    }
    
    • ローカルで試すと--changedSince masterでも動くが、github actions 上だと--changedSince origin/masterとしないとエラーが吐かれた
      • エラー内容
          ● Test suite failed to run
            fatal: ambiguous argument 'master...HEAD': unknown revision or path not in the working tree.
            Use '--' to separate paths from revisions, like this:
            'git <command> [<revision>...] -- [<file>...]'
        
  • 実行時間
    result-before

    • 修正内容に影響するテストのみが実行され、実行時間が大幅に短縮された

Vitest でブランチ間差分テストを実行したい場合

  • ちなみに vitest で同じようなことをする場合は、--changedオプションになる

まとめ

4つ対策として挙げたが、その中で

  • node_modulesのキャッシュ化
  • ブランチとの修正差分に影響ある部分だけテストを実行する

取り入れたことで、やりたかったことの

  • github actions のジョブ自体の実行時間を早くしたい。
  • github actions の実行時間無料枠を有効的に使いたい。

を実現することができた。

プルリク時のジョブ実行時間抜粋

修正量にもよるが 3m〜5m 程度でパイプラインが終わるようになった
実行時間抜粋

パイプラインの実行時間内訳

  • 主に時間がかかっているステップは2つ
  • Run cd ./front-appという step でnpm installを行うが、キャッシュが有効のため skip された
  • Testで差分実行オプションを付与したため実行時間が短縮された
    実行内訳

参考

node_modulesのキャッシュ化

ブランチとの修正差分に影響ある部分だけテストを実行する

25
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
12