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", }
"devDependencies": { "@swc/core": "1.9.1", "@swc/jest": "0.2.37", }
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', }
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')
// 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
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
- 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/restore
とactions/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
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の設計と運用