こんにちは。
株式会社アカツキゲームスで ATLAS というチームに所属してゲーム内通貨管理基盤の開発及び運用を行っています、なかひこくん (@takanakahiko) です。 最近バイクを買いました。
私の担当するゲーム内通貨管理基盤の開発現場では、「マージ待ち」なるものが存在しました。 今回は、その課題を GitHub の新機能である merge queue で解決した方法を紹介します。
この記事は 2023-07-20 時点での merge queue 及び GitHub Actions の仕様に則ったものです。 今後のアップデートによりこの記事の内容が正しくないものとなる可能性があります。
「マージ待ち」とは
私の担当するゲーム内通貨管理基盤の GitHub リポジトリでは PR のマージ後に走る、同時に実施できない 15 分程度の E2E test が存在しました。 すなわち PR をマージして 15 分程度、違うブランチをマージできないといった課題を抱えていたのです。
まず、E2E test を PR 内部の毎 commit にて行う、といった解決案を考えました。 しかし、毎 commit で行うにはとてつもなく長い時間を要します。 これは待ち時間を減らしたいといった問題の解決にはならない上に、コスト面でも良くない影響が大きいです。 また、同時実行制御がネックです。
マージ前に PR 内で特定のコメントをした場合にのみ E2E test を行う、という案も検討しました。 しかし、コメント後に「レビュー依頼待ち」が発生してしまうといった問題のすり替えにしかならないこと、コメントを忘れてマージした場合のハンドリングが厄介であること、さらにこちらも同時実行制御がネックであることを踏まえると、どうもパッとしません。
こういった「マージ待ち」のような問題は、詳細は違えど他の会社やチームでも発生するものだと思います。
merge queue というのがあるらしい
2023-02-09 に GitHub の merge queue が public beta ステータスでリリースされました。 ざっくりですが、2023-07-20 時点での概要としては以下のものとなります。
- Branch protection rule から有効化されたブランチへの PR の commit を merge queue へ入れることができる
- merge queue に入った commit から、同時実行数などの制御をもとに merge group というものが作られる
- merge group に対してマージ前に GitHub Actions の merge_group event で指定された job が実行される
- ステータスチェックやコンフリクトチェックを待ってマージを行う
同時実行数の制御を行えば、同時に同じ job が走ることも防げます。同時実行数以上の merge group が merge queue 内に存在する場合は、先に追加された merge group から順番に job が実行されます。 今回の「同時に実施できない 15 分程度の E2E test」を動かすこともできそうです。
これを用いることで「マージ待ち」が解決できるかもしれません。
merge queue の導入
Branch protection rule から Require merge queue の設定をすることで PR の画面から merge queue へ突っ込むことができるようになります。
また、以下のように GitHub Actions Workflow を設置することで、 merge group 追加時に job を実行できます。
on: merge_group: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: echo hello
簡単ですね! ...と、思いきや
merge queue の為のジョブを2回実行しなければいけない問題
merge queue についてさらに深く調べてみると以下のような仕様が発覚しました。
- merge group に対して Branch protection rule で指定された job の成功を待ってマージを行う
- branch protection に指定しないとその job が終了する前にマージされてしまう
- Branch protection rule で指定された job が成功していないと commit を merge queue へ追加できない
- 失敗はもちろん、実行すらされていない状態でも merge queue へ追加できない
ピンと来ない方もいると思うので例を踏まえて説明します。
以下のような状況を想定しましょう。
job1
を main ブランチの Branch protection rule に設定します。job1
はmerge_group
event とpull_request
event で呼ばれます。job2
(時間がかかるタスク) はmerge_group
event のみで呼ばれます。
on: merge_group: pull_request: jobs: job1: runs-on: ubuntu-latest steps: - run: # なんかする
on: merge_group: jobs: job2: runs-on: ubuntu-latest steps: - run: # 長い時間をかけてなんかする
その場合は以下のような挙動になります。
feature/a
ブランチのjob1
が失敗している場合、feature/a
は merge queue に追加することができないfeature/b
ブランチの commit が merge queue に入るとjob1
とjob2
が実行され、その後job2
の終了を待たずにjob1
の成功時点で merge される
ちなみに、 job1
の merge_group
event の指定を取り除き pull_request
event のみで動くように設定した場合、merge queue に入れる前に job1
が成功することはないため、実質的に merge queue に追加することは不可になります。
今回の「同時に実施できない 15 分程度の E2E test」は以下のようにすれば merge queue で実行できそうですね。
e2e
job を main ブランチの Branch protection rule に設定する- 以下のようなワークフローを設置する
on: merge_group: pull_request: jobs: e2e: runs-on: ubuntu-latest steps: - # 同時に実施できない 15 分程度の E2E test
ん?????
よく考えたら e2e
job を merge queue に入れる前と入れた後の2回動かさないといけないじゃん!!!!!
さらに、PR がほぼ同時に作られたら、今回導入しようと思っていた「同時に実施できない 15 分程度の E2E test」が同時に動くことになります。
これ全然意味ないどころか余計に状況を悪化させませんか!?
さてどうするか...
上記問題に対するワークアラウンド
Branch protection rule は完了済み job の「名前のみ」を参照して、 merge queue に入れても問題ないか判定します。名前さえ同じであれば branch protection に適用されます。
つまり、同じ名前の job を merge_group
event と pull_request
event で別のものとして指定することで、 merge queue への追加の判定と merge queue 内での通過の判定に別々の処理を指定することができるのです。
それを利用して、我々は以下のように問題を解決しました。
e2e
job を main ブランチの Branch protection rule に設定する- 以下の2つのようなワークフローを設置する
# merge queue への追加ができるかの判定に利用される job on: pull_request: jobs: e2e: runs-on: ubuntu-latest steps: - run: echo "dummy job for e2e branch protection rule"
# merge queue 内でマージの可否を判定する job on: merge_group: jobs: e2e: runs-on: ubuntu-latest steps: - # 同時に実施できない 15 分程度の E2E test
実際に動かしてみた
実際に Branch protection rule に設定している job 名は e2e
ではなく違う名前かつモザイクをつけていますがご了承ください。
今回は Renovate によって発行された PR を対象にします。
まずは、PR の最終コミットで実行されている、 merge queue に追加することが可能であるか判定するための job を確認します。
ここでは、 echo "e2e job works only with merge_group event"
が実行されるだけなので、1 秒程度で処理が完了しています。(上記のサンプルコードでは echo "dummy job for e2e branch protection rule"
を指定していましたが、表示内容は若干変えています)
レビュー後、 merge queue に入れるボタンを押すまでに待ち時間が発生することは殆どありません。
merge queue に追加しましたら以下のように 12 分かけて E2E test が走ります。
この時に、ちょうど Renovate を導入したので大量の PR が来ていました。 それをポチポチと merge queue に入れていたので、merge queue 内は以下のように
その後、 e2e
job は完了し、先ほどの PR は自動的に main ブランチにマージされました。
今後
今回のような問題は GitHub 上の公式フォーラムでも度々話題になっています。
現時点では最初のスレッドの下部にある「🚀 Top feature requests」の項目にも
Require different status checks for a PR's entry into the queue vs. for merging by the queue
と書いてあります。 これが取り入れられれば、Branch protection rule の設定がより良いものに変わり、今回のようなワークアラウンドが不要になりそうですね。
また、今回紹介しました方法は(あとから気がついたのですが)フォーラム上でも実際にワークアラウンドとして紹介されています。
merge queue について困ることがあったら、このフォーラムを参考にすると良さそうです。
まとめ
今回紹介した方法を用いて、無事に「マージ待ち」を解決することができました。
上記にもある通り、 Renovate なども含めると大量の PR をマージする必要がありますが、その際に「マージ待ち」が発生しないことはすごく快適です。
本記事のまとめです。
- merge queue は同時実行できない job による「マージ待ち」を解消できる場合がある
- Branch protection rule が merge queue 追加可否と merge queue 通過可否に利用されるため、指定したジョブが2回実行されてしまう
- Branch protection rule に設定した job 名で
pull_request
event にダミーステップを、merge_group
event に merge queue 通過可否に判断したいステップを設定することで、1回の実行で済む
少しクセはありますが、ぜひ皆さんも merge queue を導入してみてください。
最後に、株式会社アカツキゲームスでは一緒に働くメンバーを募集しています!
カジュアル面談もやっていますので、気軽に話す機会をいただけるとうれしいです。