Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

GitHub の merge queue で 「マージ待ち」を解消した話

こんにちは。

株式会社アカツキゲームスで ATLAS というチームに所属してゲーム内通貨管理基盤の開発及び運用を行っています、なかひこくん (@takanakahiko) です。 最近バイクを買いました。

私の担当するゲーム内通貨管理基盤の開発現場では、「マージ待ち」なるものが存在しました。 今回は、その課題を GitHub の新機能である merge queue で解決した方法を紹介します。

この記事は 2023-07-20 時点での merge queue 及び GitHub Actions の仕様に則ったものです。 今後のアップデートによりこの記事の内容が正しくないものとなる可能性があります。

「マージ待ち」とは

私の担当するゲーム内通貨管理基盤の GitHub リポジトリでは PR のマージ後に走る、同時に実施できない 15 分程度の E2E test が存在しました。 すなわち PR をマージして 15 分程度、違うブランチをマージできないといった課題を抱えていたのです。

E2E test 実行中は次の PR をマージできない

まず、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 へ突っ込むことができるようになります。

Branch protection rule から 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 に設定します。
  • job1merge_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 に入ると job1job2 が実行され、その後 job2 の終了を待たずに job1 の成功時点で merge される

上記の箇条書きを説明した図

ちなみに、 job1merge_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 に入れるボタンを押すまでに待ち時間が発生することは殆どありません。

pull_request event の job は 1 秒で終了している

merge queue に追加しましたら以下のように 12 分かけて E2E test が走ります。

merge_groupe event の job は前提 job を含めて 12 分を要している

この時に、ちょうど Renovate を導入したので大量の PR が来ていました。 それをポチポチと merge queue に入れていたので、merge queue 内は以下のように

merge queue に 4 つの merge queue が並んでいる。先頭が実行中であるため、残り3つは待機中。

その後、 e2e job は完了し、先ほどの PR は自動的に main ブランチにマージされました。

マージキューへの追加後にマージが自動的に行われている

今後

今回のような問題は GitHub 上の公式フォーラムでも度々話題になっています。

github.com

現時点では最初のスレッドの下部にある「🚀 Top feature requests」の項目にも

Require different status checks for a PR's entry into the queue vs. for merging by the queue

と書いてあります。 これが取り入れられれば、Branch protection rule の設定がより良いものに変わり、今回のようなワークアラウンドが不要になりそうですね。

また、今回紹介しました方法は(あとから気がついたのですが)フォーラム上でも実際にワークアラウンドとして紹介されています。

github.com

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 を導入してみてください。

最後に、株式会社アカツキゲームスでは一緒に働くメンバーを募集しています!

herp.careers

カジュアル面談もやっていますので、気軽に話す機会をいただけるとうれしいです。