Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions .asf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,22 @@ github:
# ALLGREEN: every entry's own build has to be green, not just the
# last one's. HEADGREEN would let an entry whose build failed ride
# in on a green head, which papers over a flake but equally papers
# over a real failure that a later entry happens to mask.
# over a real failure that a later entry happens to mask. It would
# also let untested code in: `merge_group.base_sha` is the queue
# commit of the entry ahead, so each entry's build runs only the
# suites its own changed files select, and a green head says
# nothing about the suites an earlier entry needed.
grouping_strategy: ALLGREEN
# Speculation depth: how many merge_group builds run at once. Each
# queue entry gets its own build of itself plus everything ahead of
# it, and merge limits do not combine those builds, so this is the
# throughput dial. At 1 every merge serializes behind a ~2.5h
# pipeline; 2 sustains roughly 19 merges a day, against a median of
# 6 and a busiest-day 22 over the last month. Raise it if the queue
# backs up, at the price of that many concurrent pipelines.
max_entries_to_build: 2
# throughput dial. In September 2026 a full pipeline took a median
# of 92 minutes, so 2 capped a queue that never ran dry at roughly
# 30 merges a day while approvals arrived at about 20 a day, and
# the backlog grew. 4 roughly doubles the cap. The price is that
# many concurrent pipelines, and more rebuilds when an entry fails,
# since every entry behind it that was already building restarts.
max_entries_to_build: 4
# These only cap how many already-green entries land in one merge;
# they do not batch the builds. No minimum and no wait, so a lone
# PR merges the moment its build is green.
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,11 +525,15 @@ head, so a semantic conflict between two PRs that each pass in isolation is
caught before either lands.

The `merge_queue` rule parameters in `.asf.yaml` are the tuning dials, and
`max_entries_to_build: 2` is the one that matters. Every entry gets its own
`max_entries_to_build: 4` is the one that matters. Every entry gets its own
`merge_group` build — [merge limits do not combine
builds](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue) —
so this caps how many pipelines are in flight, and with them how fast the queue
drains: roughly 19 merges a day at the ~2.5h pipeline we see today.
drains. With the ~90 minute pipeline of September 2026, a queue that never runs
dry merges roughly 58 PRs a day at 4, against roughly 30 at 2. Each entry's
build is based on the queue commit of the entry ahead (`merge_group.base_sha`),
so it runs only the suites its own changes select; `ALLGREEN` is what makes
that sufficient.
`max_entries_to_merge: 5` only says how many already-green entries land in one
merge operation, and saves no CI at all. The saving in this design comes from
the PR tier being small, not from batching inside the queue.
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,11 @@ jobs:
elif [[ "$EVENT_NAME" == "pull_request" ]]; then
git diff --name-only "$PR_BASE_SHA"..."$PR_HEAD_SHA" > changed_files.txt
elif [[ "$EVENT_NAME" == "merge_group" ]]; then
# The queue branch is built on top of base_sha, so this covers every
# entry batched into this merge group, not just the newest one.
# base_sha is the queue commit of the entry ahead of this one, and
# main only for the entry at the front, so this covers this entry's
# own changes. The entries ahead were diffed by their own
# merge_group runs; the queue's ALLGREEN strategy is what makes
# that enough (see the Merge Queue ruleset in .asf.yaml).
git diff --name-only "$MQ_BASE_SHA"..."$MQ_HEAD_SHA" > changed_files.txt
else
# push to main; first push to a branch has all-zero before sha
Expand Down
17 changes: 9 additions & 8 deletions docs/source/contributor-guide/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,17 @@ The pull request's own checks do not have to be finished, though it is polite no
request whose PR tier is red.

GitHub then builds a temporary branch named `gh-readonly-queue/main/...` containing the pull
request's commits squashed on top of the current `main`, batched with up to four other queued pull
requests, and runs `ci.yml` against it with a `merge_group` event. When `Required Checks` on that
branch is green, every pull request in the batch merges. If it is red, GitHub removes the pull
request whose entry failed, rebuilds the remaining entries without it, and records the removal on
the pull request's timeline.
request's commits squashed on top of `main` and of every entry ahead of it in the queue, and runs
`ci.yml` against it with a `merge_group` event. Up to four entries build at once. An entry merges
once `Required Checks` on its branch is green and every entry ahead of it has merged; up to five
green entries land together. If an entry's build is red, GitHub removes that pull request,
rebuilds the entries behind it without it, and records the removal on the pull request's
timeline.

The queue tests the merge result rather than the pull request head. That is the point of it: a
semantic conflict between two pull requests that each pass in isolation is caught before either
lands. It also means a pull request can be evicted for a failure it did not cause on its own,
because `main` moved or because another entry in the batch broke the combined tree.
because `main` moved or because an entry ahead of it broke the combined tree.

## When a queue run fails

Expand All @@ -230,8 +231,8 @@ through these in order:

3. **Reproduce it on the pull request.** Merge `main` into the branch so the pull request head
matches what the queue tested, then apply the label for the suite that failed. If the labeled
run passes, the failure came from the batch, not from this change, and re-queuing is the right
next step. If it fails, fix it on the branch like any other CI failure.
run passes, the failure came from the entries ahead of it, not from this change, and
re-queuing is the right next step. If it fails, fix it on the branch like any other CI failure.

4. **Check for flakiness.** A test that is flaky in the queue tier blocks everyone's merges, not
just one pull request. If a queue failure looks like a flake, do not just re-queue: file or
Expand Down
Loading