Github Actionsの悩ましいところ

昨日Githubがリリース出してGithub Actionsがprivateでfreeプランでも使えるようになりました。

github.blog

そこでGithub Actionsでキャッシュを設定するにあたり調べたことをまとめておきます。

help.github.com

github.com

CircleCIとかBitriseとかのサービスから移行したとき、混乱するのがキャッシュ周りだと思います。 実行環境のスペックを見てみると分かる通り、環境自体にそこまでの差は感じません。

Github Actionsのキャッシュが独特な点は、Github Repositoryのブランチが影響する点です。 キャッシュの key はCircleCIのキャッシュと同じような key の一致判定と、一致しなかった場合に検索される restore-keys になります。 (Bitriseのキャッシュはあれはあれで特殊なので、比較は控えておきます。。。)

- uses: actions/cache@v1
  with:
    path: ~/.gradle/caches
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
    restore-keys: |
      ${{ runner.os }}-gradle-

https://github.com/actions/cache/blob/master/examples.md#java---gradle

保存されるキャッシュは「1つのRepositoryあたり5GBまで」です。 CircleCIやBitriseでは明示的に制限されていない箇所になるため、大量のPRが同時に存在したりするケースでは期待するキャッシュの動作を異なることがあり得ます。

https://help.github.com/ja/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy

扱いが難しいのは、キャッシュのアクセス制限です。 キャッシュへのアクセスについての制限から引用をします。

pull_requestのclosedイベントの場合を除く、push及びpull_requestイベントで起動されたワークフロー内のキャッシュにのみアクセスできます。 詳しい情報については、「ワークフローをトリガーするイベント」を参照してください。

ワークフローは、現在のブランチ、baseブランチ(フォークされたリポジトリのbaseブランチを含む)、デフォルトブランチ(通常はmaster)で作成されたキャッシュにアクセスし、リストアできます。 たとえばデフォルトブランチのmasterで作成されたキャッシュは、どのプルリクエストからもアクセスできます。 また、feature-bブランチがfeature-aをbaseブランチとして持つなら、feature-bで起動されたワークフローはデフォルトブランチ(master)、feature-a、feature-bで作成されたキャッシュにアクセスできます。

アクセス制限は、異なるワークフローとブランチ間の論理的な境界を作成することによって、キャッシュの分離とセキュリティを提供します。 たとえばfeature-aというブランチ(baseブランチはmaster)用に作成されたキャッシュは、feature-bというブランチ(baseブランチはmaster)へのプルリクエストからはアクセスできません。

なお"baseブランチ"はGitの概念にない(調べた限り……)ため、GithubのPRで指定するマージ先のブランチを指していると思われます。

help.github.com

CircleCIのように key が一致したかどうかだけで考える必要がないため、誤ったビルドキャッシュを利用することによるビルドクラッシュが防がれます。一方で、先述の5GB制限もあるため想定外のタイミングでキャッシュがヒットしないことも発生します。

つまり、ステップの記述と動作までは問題なく済んだ場合でも、キャッシュが想定通りに扱われているかは注意深く調整する必要があります。 一度作ったキャッシュを使い回すことでビルド時間を大幅に短縮していた場合には、想定外のビルド時間延長が発生することがあり得るため、ご注意ください。

ここまでが少し"ネガティブ"な話をしてしまったので、最後に"ポジティブ"な話を一つ。

Github Actionsのcacheステップは、呼び出した順に"restore"を実行し、その逆順に"store"を実行します。実例は下記のステップとその実行結果を見てみてください。

Studyplus-Android-SDK/unit_test.yml at 2.6.2 · studyplus/Studyplus-Android-SDK · GitHub

Update README · studyplus/Studyplus-Android-SDK@78bb889 · GitHub

このためキャッシュ→タスクの実行という処理を書けば、キャッシュのリストア→タスクの実行→キャッシュのストアがタスクが実行した時のみ行われるようになります。 結果として、ビルドキャッシュの保存が非常に簡単に行えるようになりました。

キャッシュへのアクセス制限にある通り"baseブランチ"で作成されたキャッシュを利用することができるため、適切に扱えば機能開発時のDangerチェックを簡単に差分ビルドで行うことができるようになっています。

自分もまだまだGithub Actionsに入門したばかりで、できることはわかっていても、なかなか最適なステップを組むことができていません。 是非、便利な使い方やより正しい理解などを指摘いただければと思っています。