GitHub Actionsで並列処理を作っていてヒヤッとした話

具体的な並列処理に関しては、会社ブログなどで公開するとは思いますが、GitHub Actionsで並列処理を作っていてヒヤッとした話を書いておきます。

TL; DR

  • 1Workflowの合計時間ではなく、1Jobごとの時間でBillable timeを算出
  • 10sec程度で終わるJobを100並列にすると、Workflowの合計時間が20分程度だとしてもBillable timeは100分になる
  • Jobを変に並列化するとすぐに無料枠が消費される
  • Action実行後すぐにBillable timeを見ても0なので、必ず時間をおいて確認する

GitHub Actionsの課金について

About billing for GitHub Actions に詳細が書いてありますが、大まかにまとめると、以下のような特徴になります。

  • Public repositoryであれば基本無料(一部例外あり)
  • Internal, Private Repositoryは無料枠の範囲内で利用可能(従量課金で増枠可)
  • 従量課金の単位は分
  • 実行時間にOS別の乗数がかけられた値がBillable time
  • 実行時間が0秒以外の場合、単位が分になるように切り上げ(12秒なら1分)

となります。
また、Billable timeはWorkflowの1実行の合計(Jobの合計値)ではなく、JobごとにBillable timeが算出されます。

この仕様を甘くみていて、少しだけヒヤッとしました。

200並列で実行する

ある処理をmatrixを使って200並列で実行しようとしていました。

200並列ではないですが、以下がWorkflowのイメージとしては近いです。

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - uses: actions/checkout@v3
      - id: set-matrix
        run: |
          echo "matrix={\"number\": [$(seq -f '"%g"' -s ',' 1 200)]}" >> $GITHUB_OUTPUT

  echo:
    runs-on: ubuntu-latest
    needs: prepare
    strategy:
      matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }}
    steps:
      - uses: actions/checkout@v3
      - run: sleep 10; echo ${{ matrix.number }}

これを書いていた時には、prepareとcheckの合計時間を元にBillable timeが算出されると思っていたのですが、
先に書いたようにJobごとにBillable timeが算出されるため、
checkが10秒で終わるとしてBillable timeは100分になります。

つまりはGitHubの無料アカウントで利用可能な2,000分の1/20である100分を使うことになります。
Teamでも3,000分なのでかなり痛いことがよく分かるかと思います。

以下は、200並列した方に関しては個人ブログに貼るのは憚れるので、個人のPrivateレポにある実行ログの一部を張っておきます。 ' 200並列ではないもの、12秒程度のJobでBillable timeが1分になっていることが分かります。

これが200並列ということは分かりますね?

Billable timeが0になる?

実行後すぐに見ても、Billable timeが0になる場合があります。
以下は、サンプルではったWorkflowを実際に動かしたものなんですが、Billable timeが0でした。

これは、Billable timeがかかっていないわけではなく、まだ計算されていないだけです。
しばらく時間をおくと以下のようになります。

並列処理を組む時は、並列数によってはこの辺を注意しながら組むと良いですね。

さいごに

ヒヤッとしたといっていた通り、GitHub Enterpriseを使っていたのもあり大きな問題にはなりませんでしたが、
それでもかなり消費した枠は大きいものだったので反省しています。

GitHub Actionsで並列処理を組む場合は、最小単位が分でJobごとに算出されることを考慮して設計するのが重要です。

Self-hosted runnnerで実行するようにしておけば、こういった問題は起きないはずなので、
他にも似たようなことを気づかずにやらない人が出ないことを祈ります。