はじめに
これはGitHub Actions Advent Calendar 2022 22日目の記事です。諸事情によりフライング投稿です。
GitHub Actionsのキャッシュにおいて、そのリストアと保存を別々に制御する機能が actions/cache@v3.2.0-beta.1 で実装されたので使ってみました。トピックブランチではキャッシュを保存しない、ビルドが失敗した際にもキャッシュを保存する、などこれまでは出来なかった細かい制御が可能になっています。
背景
GitHub Actionsにおいて、ダウンロード済みの依存関係などをキャッシュすることでワークフローの実行を高速化することは、一般的によく知られたテクニックです。
一方でGitHub Actionsのキャッシュはいくつかの制限があることが知られています。
- 1リポジトリあたり合計10GBまで*1
- デフォルトブランチおよびカレントブランチのキャッシュしかリストアできない*2
- Pull Requestではベースブランチのキャッシュも利用可能
- 同じキーに対するキャッシュを上書きできない*3
- 暗黙的に定義された事後処理ステップにおいてキャッシュの保存が行われるため、無関係な箇所でジョブが失敗した場合にでもキャッシュの保存がスキップされてしまう
これにより、特にキャッシュサイズが大きい場合トピックブランチで複数回キャッシュの保存が行われるとデフォルトブランチのキャッシュが消えてしまったり、依存関係のフェッチとテストを別ジョブに分けて確実に依存関係がキャッシュされるようなワークアラウンドが必要なケースがありました。
actions/cache/restoreとactions/cache/save
以前からより詳細なキャッシュの制御がしたいという要望はあり、2019年頃から以下のようなissueがありましたが、あまり進展は見られていませんでした。
しかし、2022年12月になって急にDiscussionにてrestoreとsaveに対応するactionが実装されることが発表されました。
実際に v3.2.0-beta.1 からrestoreとsaveが実装されています。
それぞれについて書くことはそんなにありません。単にキャッシュのリストア・保存が別アクションに分かれただけです。
■ 追記(2022/12/26)
actions/cache/saveとactions/cache/restoreはv3.2.0でGAになりました。
以降のサンプルコードの@v3.2.0-beta.1
は @v3
で読み替えても動作します。
追記終わり
ユースケースの紹介
実際に背景で説明したいくつかの課題をこれで解決することが出来るので一例を紹介します。
トピックブランチではキャッシュを保存しない
2GBのランダムなダミーデータをキャッシュに保存してみることにします。ただし、トピックブランチではキャッシュの保存をスキップします。
name: Save cache only on main on: [push] jobs: run: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - uses: actions/cache/restore@v3.2.0-beta.1 with: path: | ./large-object key: ${{ runner.os }}-${{ runner.arch }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-${{ runner.arch }}- - name: Generate random file if needed run: | if [ ! -f ./large-object ]; then base64 /dev/urandom | head -c 2048M > large-object fi - uses: actions/cache/save@v3.2.0-beta.1 if: github.ref == 'refs/heads/main' with: path: | ./large-object key: ${{ runner.os }}-${{ runner.arch }}-${{ github.sha }}
キャッシュがevictされる問題に対応出来るだけでなく、そのトピックブランチでしか有効でないキャッシュの保存にかかる時間をスキップできることも大きいです。サイズの大きなキャッシュのアップロードを無効化するだけで場合によっては数十秒〜数分の短縮に繋がることもあります。
ただし、これはトピックブランチで長い期間開発する場合にはキャッシュがないことによりむしろ実行時間が延びる可能性があります。
常にキャッシュを保存する
例えばテストが失敗するケースでも、キャッシュを保存したいというようなユースケースです。特にflakyなテストが存在する場合には有用かもしれません。
name: Save cache always on: [push] jobs: run: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - name: Step to fail run: | echo hello > ./file-to-cache false - uses: actions/cache/save@v3.2.0-beta.1 if: always() with: path: | ./file-to-cache key: ${{ runner.os }}-${{ runner.arch }}-${{ github.sha }}
途中のステップで失敗しているので、ジョブ全体のステータスとしては失敗になります。
restoreとsaveで異なるkeyを使う
これがどれくらい需要のあるユースケースなのかはわかりませんが、これまでは地味にできなかったことです。
hashFiles
などを使ってハッシュを計算する際、これまでは最初のリストア時に計算されたkeyが保存時にもそのまま利用されていました。つまりそのビルド中に hashFiles
による計算結果が変わる場合に対応出来ていませんでした。例として以前の挙動を確認してみます。
途中のステップで hashFiles
の対象としているファイルを作成しています。最初の評価時点ではファイルが存在しないため、 hashFiles('**/hello.txt')
は空文字列になります。
name: Old behavior on: [push] jobs: run: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - uses: actions/cache@v3 with: path: | ./hello.txt key: ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/hello.txt') }} restore-keys: | ${{ runner.os }}-${{ runner.arch }}- - name: Generate hashFiles targets run: | echo "hello" > hello.txt
actions/cache/saveを使って同じ事をすると、保存時に hashFiles
の結果が再度評価されていることがわかります。
name: hashFiles get different result on: [push] jobs: run: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - uses: actions/cache/restore@v3.2.0-beta.1 with: path: | ./hello.txt key: ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/hello.txt') }} restore-keys: | ${{ runner.os }}-${{ runner.arch }}- - name: Generate hashFiles targets run: | echo "hello" > hello.txt - uses: actions/cache/save@v3.2.0-beta.1 with: path: | ./hello.txt key: ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/hello.txt') }}
hashFiles
を使う場合に限らず、restoreとsaveで異なるkeyを指定することが可能になっているので刺さる人には刺さるかも知れません。
まとめ
ライトなユースケースでは従来通り actions/cache
をそのまま利用するのがわかりやすく、記述も容易であるため完全に置き換わることはない印象です。
一方、より詳細なキャッシュの制御を求める人にとっては待望の新機能になりそうです。キャッシュサイズの大きさが気になっている人は、とりあえずトピックブランチでのキャッシュ保存を辞めてみると良いかも知れません。