Timee Product Team Blog

タイミー開発者ブログ

GitHubマージキューのエラー通知

こんにちは、タイミーでPlatform Engineerをしている近藤です。

マージキュー上のエラー通知について

GitHubのマージキューは、チームが効率的かつ安全にコードをリリースするために欠かせない仕組みです。特に、大規模なチームや頻繁にコードをデプロイするプロジェクトでは、マージキューがCI/CDプロセスの核となります。しかし、マージキュー上でエラーが発生した際、その通知を迅速に受け取ることが極めて重要です。通知が遅れたり、見落とされたりすると、次のような問題が生じる可能性があります。

リリースの遅延

エラーが発生すると、問題のある変更は差し戻されます。
その結果、後続のPRのベースが変更されるため、マージキュー上でのCI処理を再実行する必要が生じます。
これにより、本来スムーズに進むべきリリースサイクルが停滞し、開発スピードが著しく低下します。

開発者体験(DX)の低下

マージキューでのエラー通知が適切に行われないと、開発者が混乱したり、不要な手戻り作業を強いられたりします。これにより、開発者のモチベーションや生産性の低下を招くことにもなります。

以上の理由から、GitHubのマージキューでエラーが発生した際の通知を確実に行うことは、開発効率を保ち、迅速な問題解決を促すために不可欠です。通知の仕組みを整備し、チーム全体が即座に問題に対処できる環境を整えることが求められます。

エラー通知の課題

単にワークフロー中にエラー通知のジョブを入れるだけでは、先行してマージキューに積まれたPRにCIエラーが発生する内容が含まれている場合、後続の人にも不要なエラー通知が届いてしまいます。これを回避するために、GitHubのconcurrency機能を活用してCIを適切にキャンセルする仕組みを導入しました。

sequenceDiagram
    autonumber
    participant DevA as 開発者 A
    participant DevB as 開発者 B
    participant Queue as マージキュー
    participant CI as CI (GitHub Actions)
    participant Master as master ブランチ

    DevA->>Queue: PR A をキューに追加
    DevB->>Queue: PR B をキューに追加

    par
        Queue->>CI: PR A + master のテスト
        Queue->>CI: PR B + PR A + master のテスト
    end

    CI--x Queue: PR A + master でテストエラー

    Queue->>DevA: PR A がテストエラーになりました
    Queue->>DevB: PR B がテストエラーになりました

    Queue->>Queue: PR B + master の再エンキュー
    Queue->>CI: PR B + master のテスト
    CI-->>Queue: PR B のテスト合格

    Queue->>Master: PR B を master にマージ

PR AとPR Bがマージキューに積まれた状態で、先行するPR Aでエラーが発生すると、PR Bは自動的にPR Aの内容を除外した状態で再度マージキューに入れられます。 この際、マージキューの実行時に作成されるブランチ名は都度変化します。よって単純にブランチ名をキーとした以下の設定では、期待通りに動作しません。

concurrency:
  group: ${{ github.ref_name }}
  cancel-in-progress: true

そこで、マージキュー上で作成されるブランチ名の規則性を利用します。ブランチ名にはPR番号が含まれているため、ワークフロー内でブランチ名からPR番号を抽出するジョブを追加しました。 最終的に採用したワークフローは以下の通りです。意外と知られていませんが、concurrencyキーワードはジョブレベルでも指定可能です。

name: ci

on:
  push:
    branches:
      - '**'
      - '!master'
      - '!gh-readonly-queue/**'
    tags-ignore:
      - '*'
  merge_group:

permissions:
  id-token: write
  contents: read
  pull-requests: write

jobs:
  concurrency_key:
    runs-on: ubuntu-latest
    outputs:
      key: ${{ steps.extract_pr.outputs.key }}
    steps:
      - id: extract_pr
        name: Extract PR number from branch name
        shell: bash
        run: |
          if [[ ${{ github.event_name }} == 'merge_group' ]]; then
            full_ref="${{ github.ref_name }}"
            pr_number=$(basename "$full_ref" | cut -d- -f2)
            echo "key=$pr_number" >> "$GITHUB_OUTPUT"
          else
            echo "key=${{ github.ref }}" >> "$GITHUB_OUTPUT"
          fi

  ci:
    needs: concurrency_key
    concurrency:
      group: ${{ github.workflow }}-${{ needs.concurrency_key.outputs.key }}
      cancel-in-progress: true
    uses: ./.github/workflows/_ci.yml
    with:
      skip: ${{ github.event_name != 'merge_group' }}
    secrets: inherit

  merge_group_notify:
    needs: ci
    if: ${{ failure() && github.event_name == 'merge_group' }}
    uses: ./.github/workflows/_merge_group_notify.yml
    with:
      role_to_assume: arn:aws:iam::012345678901:role/example
      notification_type: failure
    secrets: inherit

上記の仕組みを導入することで、先行するPRでテストエラーが発生しても、その影響で後続PRに対して不要なエラー通知が送信されることを防げます。

sequenceDiagram
    autonumber
    participant DevA as 開発者 A
    participant DevB as 開発者 B
    participant Queue as マージキュー
    participant CI as CI (GitHub Actions)
    participant Master as master ブランチ

    DevA->>Queue: PR A をキューに追加
    DevB->>Queue: PR B をキューに追加

    par
        Queue->>CI: PR A + master のテスト
        Queue->>CI: PR B + PR A + master のテスト
    end

    CI--x Queue: PR A + master でテストエラー

    Queue->>DevA: PR A がテストエラーになりました

    Queue->>Queue: PR B + master の再エンキュー
    alt 失敗通知をキャンセル
        Queue -x DevB: PR B がテストエラーになりました (キャンセル)
    end
    Queue->>CI: PR B + master のテスト
    CI-->>Queue: PR B のテスト合格

    Queue->>Master: PR B を master にマージ

ただし、PR AとPR Bがほぼ同時にマージキューへ投入された場合など、極めて稀なタイミングによっては、キャンセル処理が間に合わず後続のPRに対して不要なエラー通知が送信されてしまうケースが残ってしまいます。 こうしたエッジケースまで完全に抑制しようとすると、ワークフロー全体の実装が大幅に複雑化し、運用・保守コストも増大してしまう懸念があります。 そのため、今回は「不要な通知の発生を現実的な範囲で最小化しつつ、実装のシンプルさを優先する」という方針を選択しました。

まとめ

本仕組みによって、マージキュー運用時の不要なエラー通知を大幅に削減しつつ、チーム全体の開発効率と安心感を両立できるようになりました。 今後も、より良い開発体験を目指して、現場の実情やフィードバックを取り入れながら、継続的な改善を進めていきます。