GitHub Actions - キャッシュ

# 内容

GitHub Actionsでのキャッシュの使用方法について記載する

キャッシュは、処理上で使用するパッケージの再利用によってWorkflowの高速化や、
Job間でファイルの受け渡し等に利用できる

[actions/cache](https://github.com/actions/cache)は、`actions/cache`1個でキャッシュのsave/restoreする方法と、  
`actions/cache/save`, `actions/cache/restore`でキャッシュのsave/restoreする方法がある

`actions/cache`は、`actions/cache`を実行したJob終了時にキャッシュがsaveされ、`actions/cache`実行タイミングでrestoreされる
`actions/cache/save`, `actions/cache/restore`は、それぞれを実行したタイミングでsave/restoreされる

# 設計

キャッシュする際のパラメータ`path`, `key`についての検討事項

## path

インストールした依存パッケージをキャッシュする場合、  
[CI/CDでnpm ciする際は ~/.npm をキャッシュしよう](https://dev.classmethod.jp/articles/cicd-npm-ci-cache/)で記載されているように、  
`node_modules`がキャッシュされている場合に`npm ci`を実行すると`node_modules`が作り直されるためキャッシュの意味がなくなってしまうことがある  
これに対して上記記事通り、`~/.npm`をキャッシュするようにするか、  
[cache-hit](https://github.com/actions/cache?tab=readme-ov-file#skipping-steps-based-on-cache-hit)を使用して、キャッシュが存在する場合にインストールstepをスキップする対処が考えられる

## key

[ドキュメント](https://github.com/actions/cache?tab=readme-ov-file#creating-a-cache-key)にあるようにkeyに `${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}` を使用することで、  
インストールしたパッケージの依存関係（`package-lock.json`）に変化があった場合にキャッシュを更新することができる  

依存パッケージ云々とは関係なく、job間でファイルを受け渡したい場合等は、  
`${{ github.sha }}`でworkflow実行時のコミットごとにキャッシュを更新する等が考えられる  

# 実装例（依存パッケージキャッシュ）

[ドキュメント](https://github.com/actions/cache?tab=readme-ov-file#example-cache-workflow)にある通り、  
まずキャッシュをrestoreして、キャッシュが存在すればキャッシュを使用し、  
存在しなければ依存パッケージインストールを行うのが一般的と思われる  
キャッシュが存在する場合に、`cache-hit`で判定してパッケージインストールをスキップすることができるが、  
インストール済パッケージのインストールは即終了するはずなので実行してしまってもそれほど問題はないはず

以下、Node.js、Pythonの依存パッケージをインストールしてキャッシュする例  
キャッシュした依存パッケージを複数jobで使用するようなケースを想定して、  
キャッシュsave用のjobと、キャッシュをrestoreして使用するjobで分けている

```yaml
jobs:
  cache_env:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
  
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
        node-version-file: 'package.json'
        cache: npm
  
      - name: Install Dependency For Node
        shell: bash
        working-directory: ./
        run: |
          npm install -g npm@9.X
          npm ci
  
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
        python-version-file: '.python-version'
  
      - name: Install poetry
        uses: snok/install-poetry@v1
  
      - name: Install Dependency For Python
        shell: bash
        working-directory: ./
        run: |
          poetry lock --no-update
          poetry install --no-interaction
  
      - name: Cache Save
        uses: actions/cache/save@v4
        with:
        path: |
            node_modules
            .venv
        key: ${{ github.sha }}

  restore_env_job_1:
    needs: [cache_env]
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
        node-version-file: 'package.json'
        cache: npm

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
        python-version-file: '.python-version'
  
      - name: Install poetry
        uses: snok/install-poetry@v1

      - name: Restore Env
        uses: actions/cache/restore@v4
        with:
          path: |
            node_modules
            .venv
          key: ${{ github.sha }}
```

# 参考

- [ジョブを細かく分けてGitHub Actionsのテストを効率化する](https://zenn.dev/yusukehirao/articles/gh-actions-split-jobs)
- [GitHub Actions の Workflow を高速化する](https://qiita.com/qualitia_cdev/items/407b0df9c0a0f0f45bbc)
- [CI/CDでnpm ciする際は ~/.npm をキャッシュしよう](https://dev.classmethod.jp/articles/cicd-npm-ci-cache/)