KAKAMU
BlogTag

GitHub Actionsを最適化する

そもそも

GitHub Actionsを最適化するためにやったこと。

Jestの実行時間を短縮する

Barrel Fileをやめる

Barrel FileはプロダクションコードのパフォーマンスやJestの実行時間に悪影響を与える。

ts-jestをやめる

transpilerをts-jestから@swc/jestに変更した。

"devDependencies": {
  "@swc/core": "1.9.1",
  "@swc/jest": "0.2.37",
}
jest.config.js
const config = {
  roots: ['paths/to/root'],
  testMatch: [
    '**/__tests__/**/*.+(ts|tsx|js)',
    '**/?(*.)+(spec|test).+(ts|tsx|js)',
  ],
  transform: {
    // '^.+\\.(ts|tsx)$': ['ts-jest', {tsconfig: "paths/to/tsconfig.json" },],
    '^.+\\.(ts|tsx)$': ['@swc/jest'],
  },
  testEnvironment: 'jsdom',
}

注意点としては、testにspyOn()が使えなくなることと、テスト実行時に型チェックが走らなくなること。

spyOn()jest.mockなどに置き換える。

// import * as module from 'calc'
// const m = jest.spyOn(module, 'calcData').mockReturnValue('mocked')
 
import { calcData } from 'calc'
jest.mock('calcData')
 
const m = jest.mocked(calcData).mockReturnValue('mocked')

型チェックが必須の場合は別途tsc --noEmitなどを実行する。Next.jsの場合はbuild実行時に型チェックが走るので不要かも。

lintの実行時間を短縮する

差分があるファイルのみlintを適用する

jobs:
  lint:
    steps:
      - uses: dorny/paths-filter@v3.0.2
        id: changed
        with:
          list-files: shell
          filters: |
            changed:
              - added|modified: 'paths/to/files/**/*.ts'
              - added|modified: 'paths/to/files/**/*.tsx'
            changed-config:
              - 'paths/to/config'
        # 変更があったファイルに対してのみXOを実行する
      - if: steps.changed.outputs.changed == 'true' && steps.changed.outputs.changed-config == 'false'
        name: Lint changed files
        run: yarn lint ${{ steps.changed.outputs.changed_files }}
        # configに変更があった場合は全ファイルに対してlintを実行する
      - if: steps.changed.outputs.changed-config == 'true'
        name: Lint all files
        run: yarn lint paths/to/all-files

キャッシュを最適化する

前提

  • GitHub Actionsは実行時間が伸びると、料金も増大していく。
  • GitHub Actionsのキャッシュの保存容量は10GBで、それを超えると古いキャッシュから自動で削除されていく。
  • キャッシュはブランチごとにsaveされており、workflow対象のブランチは自ブランチまたはmainブランチのキャッシュにしかアクセスできない(=restoreできない)。
  • keyが完全一致している、またはrestore-keysが部分一致していたらキャッシュがrestoreされる。
  • フロントエンドの場合、yarn.lockをハッシュ化した文字列によって差分を検知して、node_modulesをキャッシュするのがセオリー。
  - uses: actions/cache/restore@v4
      with:
        path: |
          **/node_modules
        key: ${{ runner.os }}-job-name-${{ hashFiles('**/yarn.lock') }}
        restore-keys: |
          ${{ runner.os }}-job-name

何が問題か

  • キャッシュが頻繁に保存されすぎると、保存されたキャッシュがすぐに10GBに達し、頻繁にrestoreしたいキャッシュが削除されてしまう。
  • keyが同じなら保存されているキャッシュも同じはずだが、同じkeyのキャッシュが何度も保存されている。
  • キャッシュを保存する処理に数十秒かかる。
  • いわゆるfeatureブランチのキャッシュは、他のブランチからアクセスできないので、大抵の場合はrestoreされることがなく無駄。

mainブランチのキャッシュのみ保存する

mainブランチのキャッシュのみを保存し、そのキャッシュをfeatureブランチがrestoreする構成にすれば、大抵の場合は最適化される。

actions/cacheの代わりにactions/cache/restoreactions/cache/saveを使い分けることで、mainブランチのみキャッシュを保存することができる。

jobs:
  lint:
    steps:
      - uses: actions/cache/restore@v4
        with:
          path: |
            **/node_modules
          key: ${{ runner.os }}-job-name-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-job-name
      - run: yarn install
      # 〜
      # 何らかのstepを実行
      # 〜
      # mainブランチの場合のみキャッシュを保存する
      - if: github.ref == 'refs/heads/main'
        uses: actions/cache/save@v4
        with:
          path: |
            **/node_modules
          key: ${{ runner.os }}-job-name-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-job-name

参考書籍

GitHub CI/CD実践ガイド――持続可能なソフトウェア開発を支えるGitHub Actionsの設計と運用

参考リンク

依存関係をキャッシュしてワークフローのスピードを上げる

フロントエンドのGitHub Actions実行時間を削減するために取り組んだこと

#IT#開発手法