> ## Documentation Index
> Fetch the complete documentation index at: https://docs.brussle.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Control what a change re-judges

> A change to a document that other answers read reaches every affected answer once, with no one to confirm it, within a cost you confirm when you create the judgment. Set the scope, bands, debounce and rolling limit that keep that cost small.

When a document that other answers read changes, Brussle judges each affected answer again, once, and no person confirms the work. You confirm the most it can cost one time, when you create the judgment, and the rolling limit holds it there.

This work is a **fan-out**. It is how Brussle runs exactly the answers that a changed record affects, and it is the main cost of a judgment that reads a document other documents share. This page shows how to estimate that cost and how to keep it small.

## How fan-out works

A fan-out starts when a change moves what a relation shows from a document that many judged documents read:

* a [referenced document](/guides/referenced-document), such as the product that many order lines name
* a document in a [named list](/guides/referenced-document#name-several-documents)
* a [block](/concepts/relations#candidates-that-share-a-key) of documents that share a key, and the [link](/concepts/relations#documents-a-matcher-linked) or [match](/guides/referenced-document#the-document-a-match-picked) a matcher made in it
* the [answers of another judgment](/guides/roll-ups) that a relation reads
* the [outcomes](/concepts/relations#what-happened-to-related-documents) you post for related documents

Brussle judges again each judged document that reads the changed document and is inside the judgment's **re-judge scope**. Two numbers set the cost:

> **re-judgments a month = changes a month × judged documents per change inside the scope**

One edit to a referenced document that 200,000 judged documents name can cause 200,000 evaluations. Each [control](#the-controls-in-order-of-effect) makes one of the two numbers smaller, or sets a maximum for their product.

* **Only a change to what the relation shows counts.** An edit to a field that the relation does not show, or a move inside a [band](/guides/context-recipes#bands), starts nothing and marks nothing `pending`.
* **Only `on_change` fans out.** Under `periodic` and `manual`, the change makes the answers in scope `stale`. Under `on_read`, the next get or query of an answer judges it again.
* **A fan-out has its own debounce.** It waits until the changed document settles, 10 minutes by default. The judgment's own `debounce_ms` still applies to writes to the judged documents, so a new order line is judged within seconds.
* **A change to a judged document's own related documents is not a fan-out.** A new amendment re-judges only its contract. See [related documents](/guides/related-documents).

[What the answers show](/guides/referenced-document#what-the-answers-show) covers how each answer reads while its fan-out runs, waits or leaves it out.

## What a fan-out costs

The examples use one namespace:

* It has 100,000 referenced documents. On average, 200 judged documents point at each one: 20M judged documents.
* 4% of the judged documents were created in the last 30 days, which is 8 for each referenced document. A quarter of those 8 agree with the scope's `where`, such as an open status: 2.
* The context and question of each judged document are about 900 tokens, so each evaluation is a standard [judgment](/pricing#judgments). [Pricing](/pricing) turns re-judgments into a price.

| Case | Changes a month | Judged documents per change | Re-judgments a month |
| - | -: | -: | -: |
| **A. Stable fields, narrow scope.** The relation shows a name and a status, which change about once a month for each referenced document. Scope of 30 days and `where` | 100,000 | 2 | 200,000 |
| **B. A number that changes daily, no scope.** The relation shows a raw number that you write each day, such as a stock count. All judged documents are in scope | 3,000,000 | 200 | 600,000,000 |
| B, scope of 30 days | 3,000,000 | 8 | 24,000,000 |
| B, scope of 30 days and `where` | 3,000,000 | 2 | 6,000,000 |
| **C. B with bands and the scope.** Bands hide the daily changes. The number crosses a band about once a month | 100,000 | 2 | 200,000 |

The scope makes B's judged documents per change 100 times smaller, and bands make its changes 30 times smaller. Together they bring B down to the size of A. Without bands, B with the scope and `where` is 6,000,000 re-judgments a month, twenty times the default [rolling limit](#the-rolling-limit). Brussle defers what goes past the limit. Raise the limit, or add bands.

The debounce and its ceiling hold a field that changes much more often. A number that you write each minute is 43,200 changes a month for each referenced document. The 10-minute fan-out debounce never ends for it, so the 1-hour ceiling fans out once an hour while the number keeps changing: 720 times a month. With bands, only a change of band counts. A band change that then holds causes one fan-out. A band that keeps changing fans out once an hour.

A large referenced document can have 200,000 judged documents that point at it. One change fans out to all of them and counts 200,000 against the rolling limit. With the 30-day scope and `where`, it is about 2,000.

Judged documents outside the scope cost nothing. Brussle bills nothing for a judged document whose context did not change.

## The controls, in order of effect

Change each default with a `PATCH`, with no new version. The judgment's controls are in `freshness.fanout`. `share` is a [namespace setting](#namespace-fan-out-settings).

| Control | Setting | Default | What it saves |
| - | - | - | - |
| **The re-judge scope** | `fanout.scope.created_within`, `fanout.scope.where` | 30 days; no filter | Judged documents created longer before the change, or that do not agree with `where`, keep their answer. `created_within: null` means all judged documents. `where: null` removes the filter |
| **Only shown fields count** | always on | – | A write that changes nothing the relation shows re-judges nothing |
| **Bands** | a relation's `fields` | none | A number that moves inside its band changes nothing |
| **The fan-out debounce and its ceiling** | `fanout.debounce_ms`, `fanout.max_wait_ms` | 10 minutes; 6 × the debounce, so 1 hour | A burst of changes to one document costs one fan-out. Changes that never stop cause one fan-out for each ceiling |
| **The rolling limit** | `fanout.rolling_limit` | 300,000 judged documents re-judged in any 30 days; `null` for no limit | Fan-outs cannot add up to more than you confirmed. Past the limit, the rest waits until there is room |
| **`block_cap`** | `fanout.block_cap` | 1,000 judged documents for each key value | A block that grows past it waits, so one huge key value cannot fan out to everything |
| **Background work** | namespace `fanout.share` | half of the namespace's engine requests in progress | A fan-out never slows the namespace's usual judging |

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{"freshness": {"fanout": {"scope": {"created_within": "90d"}, "rolling_limit": 6000000}}}
```

A `PATCH` like this one changes those two keys and keeps the others, `scope.where` too. `GET` returns each value in effect, and `fanout_sources` says where each one comes from: `judgment` or `default`. You do not confirm a wider scope or a higher limit, though either can make the bill much higher. The namespace [budget](/concepts/namespaces#settings) still sets the maximum spend. [Freshness policies](/guides/freshness-policies#fan-out-when-a-referenced-document-changes) lists each setting with its bounds.

### Set the scope

* **It counts from the change, not from now.** The scope of one change stays the same however long its fan-out waits.
* **Age comes from `created_at`**: when Brussle first got the document, or the creation time that the write gave. [Import](/guides/import-existing-data) judged documents with their original `created_at`, and only the recent ones are in scope. Imported without it, each one is in the default scope for 30 days.
* **Outside the scope, an answer keeps its numbers** and reads `stale` with `stale_reason` `referenced_changed` until something judges the document again.

**When other judgments read its answers.** A judged document outside the scope keeps an old answer, and a judgment that reads that answer would keep reading it, with nothing to update it. A judgment whose answers other judgments read therefore needs `"scope": {"created_within": null}` and no `where`.

* Brussle refuses a create that would read a judgment with a scope, with `invalid_request` naming the path.
* With no scope, each change re-judges every judged document that reads the changed document. Bound the judged documents with `applies_to`, such as only open orders, not with the scope.
* While the judgment has readers, Brussle refuses a `PATCH` that gives it a scope again, with `conflict` naming the readers.

## The rolling limit

`freshness.fanout.rolling_limit` is the most documents a judgment judges again in any 30 days because of changes outside the document itself: its referenced documents, its blocks, and the answers and outcomes it reads. Every judgment with a relation has one, roll-ups included. The default is **300,000**. You confirm it once, at create, priced in full as [`cost_usd_at_limit`](#what-the-replay-estimate-cannot-count). After that no fan-out waits for a person.

* **What counts.** Each re-judge counts once, when its answer lands, even when it needs no engine call. A retried re-judge counts once.
* **Within the limit**, a fan-out runs at once, with no job. Its spend shows in the namespace's spend.
* **As much as fits runs.** When a change needs more re-judges than the window has room for, Brussle runs as many as fit, at most 1,000 at a time, and defers the rest. A large upload of outcomes runs the same way.
* **Deferred, never declined.** The answers still to judge read `stale` with `stale_reason` `limit_reached`, and the change in their `referenced_changes` has `deferred: true`. The [events feed](/guides/events-feed) records one `judgment.limit_reached` event for the change. Its `deferred` is the number of judged documents still to judge:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "type": "judgment.limit_reached",
  "data": {
    "namespace": "acme/prod",
    "judgment": "agrees_with_product",
    "limit": "rolling_limit",
    "relation": "product",
    "document_id": "prod_17",
    "deferred": 48210,
    "url": "https://api.brussle.com/v1/namespaces/acme%2Fprod/judgments/agrees_with_product"
  },
  "...": "..."
}
```

* **Catching up.** Deferred work runs by itself as older re-judges leave the 30-day window; `fanout_window.rolls_at` says when the window next frees room. A `PATCH` that raises `rolling_limit` starts it without waiting, usually within a minute, and needs no confirm. A change larger than the whole limit still makes progress month by month.
* **Watch it.** No event marks the catch-up. Each deferred answer reads `stale` (`limit_reached`) until it is judged, then `fresh`, and `fanout_window.used` rises by each re-judge as it lands. Subscribe a [webhook endpoint](/guides/webhooks) to `judgment.*` to learn of a deferral when it happens.
* **A block past `block_cap`** waits as a whole for a higher cap or a finer key, whatever the window. Its `judgment.limit_reached` event has `limit: "block_cap"` and names the key value. At create, Brussle refuses a key value whose block is already past the cap, with `invalid_request`.
* **No limit.** `"freshness": {"fanout": {"rolling_limit": null}}`, at create or in a `PATCH`, re-judges every document in scope on every change, with no deferral. `cost_usd_at_limit` is then `null`, and only the namespace's budget limits what fan-out spends. Without bands, a field that changes often then re-judges everything in scope each time. A higher limit with a known cost is safer.

## What the replay estimate cannot count

Before a judgment with a relation runs under `on_change`, its create returns the [replay estimate](/guides/related-documents#the-replay-estimate) of the monthly cost and creates nothing until you send `confirm: true`. A change of its policy to `on_change` does the same.

The replay cannot count fan-out: it cannot tell which past writes to a referenced document changed what the relation shows. For such a judgment it returns `"excludes": ["fanout"]` and `lower_bound: true`, and the dashboard shows the figures as "at least, not counting fan-out". It still counts the writes to the judged documents themselves.

The rolling limit sets the most fan-out can add. The response gives its price beside `replay`, in `fanout`, at the sample's judgments for each answer and your organization's tiers:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{"fanout": {"rolling_limit": 300000, "cost_usd_at_limit": 75.0}}
```

`cost_usd_at_limit` is the most fan-out can cost in any 30 days, and you confirm it with `confirm: true`. Here, 300,000 standard judgments at the first tier are \$75. With `"rolling_limit": null` in the create, both values are `null`. A judgment in a chain also gets `graph_ceiling`, the same bound summed over every judgment that reads it. See [roll-ups](/guides/roll-ups).

## Namespace fan-out settings

`PATCH /namespaces/{ns}` sets how fan-out runs for every judgment in the namespace:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{"fanout": {"share": 0.5}}
```

* **`share`** is the most of the namespace's engine requests in progress that fan-out can use. It is more than 0 and at most 1, and the default is 0.5. Fan-out always gets at least one request, and the rest do the namespace's usual judging. `GET` returns the value in effect. See [namespaces](/concepts/namespaces#settings).
* **Fan-out is background work.** It runs at a lower priority than the namespace's own changes, like a backfill.
* **A budget pause stops fan-out**, as it stops all judging.

## Not available yet

* Fan-out in the replay estimate.
* A limit on the total fan-out of a namespace, other than its budget and each judgment's rolling limit.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.