# Merge more changes a day

> Let idle executors check queued changes early, carry records across unrelated merges, and check several changes as one tree.

Source: https://getbay.io/docs/throughput (HTML).

One main branch merges one change at a time, and each change waits for its own check run. One check slot runs at most 1,440 divided by the check minutes runs a day. Without these settings, one main branch merges at most that many changes. Three settings let a change merge on a check run made earlier, or shared with other changes. Each is off by default, and each is a rule of evidence reuse, never a weaker verdict. With `batch`, members merge on the check of the batch's last commit; with `footprint`, a record names its source.

Merges still go one push at a time, in queue order. Every merge still rests on evidence, and its result names what that evidence covers. A missed prediction, a changed read set or an unknown never counts as a pass: the check runs. The gain depends on your queue, your executors, your paths and how often your checks fail.

## When to use these settings

- **Executors sit idle while the head checks**: set `ahead`, so they check the entries behind it.
- **Most changes touch separate paths**: set `footprint = true` on narrow checks, and `footprint_lanes`.
- **Checks are slow and rarely fail**: set `batch`, so one run covers several changes.
- **Checks fail often**: `max_failure_rate` stops batches until the failure rate falls.
- **One change runs several slow checks**: `[executor] parallel_checks` runs them at once, with no evidence reuse.

## Before you start

Each setting needs `[policy] evidence_reuse = "exact-inputs"`, or bay refuses the config. Each also needs a key registry. Without one, the settings do nothing. Each executor is its own producer and check slot, so more executors give more early checks.

```toml
# .bay/config.toml
[policy]
evidence_reuse = "exact-inputs"    # every throughput setting needs it

[executor]
ahead = 4                          # check up to 4 queued entries early; at most 16
footprint_lanes = true             # check an entry beside the head when paths are disjoint
batch = { min = 2, max = 8, max_failure_rate = 0.3 }
```

## Check queued changes early

With `ahead = N`, an executor that does not hold the head checks the next N entries now. It checks each on the base the entries ahead of it predict: main with each of them merged, in order. It merges nothing and takes no ownership. The operation lists the run under `prechecks`. When the entry reaches the head and main matches the prediction, it merges on that evidence with no new run. When the prediction misses, the record is about another tree, and bay ignores it. A miss wastes that work, and never counts as a pass.

```toml
[executor]
ahead = 4
```

> bay predicts that an entry ahead that conflicts, or whose work is already on main, does not merge.
> An entry ahead that changes the policy or an adapter path stops the prediction there.
> Two executors that check one entry on one base run it at most twice.

## Carry a record across unrelated merges

Without this, any merge to main makes a queued candidate's records stale, and its checks run again. `footprint = true` on a `[[check]]` says its verdict reads only its `paths`, else its `when_paths`. bay then stamps each record with digests of that read set, in the candidate and in its base. A later candidate of the same operation, with the same stamp, policy and fixtures, gets the record carried. The carried record names the record it came from, and the check does not run. Carrying needs `[executor] footprint_lanes = true`. A check whose `inputs` name `commit` or `history` never carries.

```toml
# .bay/policy.toml
[[check]]
id = "api"
environment = "linux-x86_64"
command = "scripts/bay-check api"
adapter = ["scripts/bay-check"]
expected_minimum = 50
when_paths = ["api/"]
footprint = true                   # the verdict reads only api/
```

> Set it only on a check whose verdict depends on nothing outside that read set.
> A suite that imports a library from elsewhere would carry a pass onto a tree it never judged.
> bay reads `footprint` at the base, and it enters the check's definition digest.
> So a candidate cannot grant it to itself, and no record made before it counts.
> `footprint = true` without `paths` or `when_paths`, or with `materialize = "changed"`, makes the policy invalid.

## Check beside the head

An entry's footprint is the union of the `when_paths` of the checks its diff requires. A required check with no `when_paths` makes the footprint the whole tree. With `footprint_lanes = true`, an executor checks an entry early when its footprint is disjoint from every entry ahead. It checks it on the current main, so only `footprint = true` records carry over to its final candidate. Merges stay in queue order: a disjoint entry never passes a blocked head. If every required check carries over, the entry then merges with one push and no new run.

```toml
[executor]
footprint_lanes = true
```

> bay treats two footprints as disjoint only when the literal prefixes of their globs show it.

## Check several changes as one tree

With `batch`, the head's executor takes the head and the live entries behind it, in queue order. It checks their combined tree once, on the current main. On a pass, every member merges in one push, and main moves once, to the last member. Each member keeps its own commit, `Change-Id` and trailers, plus a `Bay-Batch: <id> <i>/<k>` trailer. Each member's result names the batch under `detail.batch`, because its own tree was not checked alone. With `footprint_lanes`, a batch takes only entries whose footprint overlaps the head's.

```toml
[executor]
batch = { min = 2, max = 8, max_failure_rate = 0.3 }
```

### When a batch fails

- bay bisects over prefixes of the batch on the same main, so no prefix runs twice.
- It finds the largest passing prefix and the first failing member, the culprit.
- When two members or more pass, that prefix merges as one batch.
- The culprit runs alone when it reaches the head, judged by its own candidate.
- When fewer than two pass, no batch merges. The head takes its usual path on its own records.
- The culprit and the members behind it keep their places in the queue.

### When no batch forms

- No batch forms while the recent failure rate is above `max_failure_rate`, 0.3 by default.
- That rate reads the last 20 check outcomes of finished queue places.
- With `footprint_lanes`, it reads only the places whose footprint overlaps the head's.
- A batch never holds a lane change, a change with named inputs, a policy change or a known culprit.
- A policy with `unchecked = true`, or a check keyed by commit, never batches: `inputs` `commit` or `history`, or `materialize = "changed"`.
- While main is held, no batch forms.

## On a host from `bay host init`

- The host admits a `Bay-Batch` commit only within a whole batch, in one push.
- The config at the batch's base must set `batch`, or the host refuses the push.
- The host judges the last member's evidence by the policy at the batch's base, over the whole diff.
- A policy repair never merges in a batch.

## What these settings cannot do

- They never merge out of queue order, even for a disjoint entry behind a blocked head.
- They never turn an unknown, a missed prediction or a changed read set into a pass.
- They do nothing without exact-inputs reuse and a key registry.

## Limits

One check slot runs at most 1,440 divided by the check minutes runs a day. With 12-minute checks, that is 120 a day. With `batch`, one run can merge up to `max` changes. `ahead` and `footprint_lanes` run more checks side by side, but only with more executors. One push at a time stays the single commit point, so pushes still go in order. bay states no number for the gain. Treat any ratio as a target, and measure your own.

- `ahead`: at most 16 entries.
- `[executor] parallel_checks`: up to 16 of one candidate's checks at once, on one box. It needs no evidence reuse.
- `batch`: at most 64 members, 8 by default, and never fewer than two.
- `max_failure_rate`: from 0 to 1, 0.3 by default, over the last 20 outcomes.
