Define one
The relation’sjoin is {"theirs": "id", "mine": "attributes.<name>"}: the judged document’s mine attribute holds the referenced document’s id. In this example, documents of kind order_line each point at a product through attributes.product_id:
applies_tolimits which documents the judgment judges, answers and bills: here the order lines, not the products.- One document. The relation reads at most one, so it takes neither
last_nnorwindow: sending either isinvalid_request.match,fieldsandaggregatework as for any relation, sorelated.producthas at most one record, andcountis 0 or 1. - What it points at. The judged document’s
mineattribute names the referenced document when it holds a valid document id. When it is missing, is not a string, or names a document that does not exist or does not matchmatch, the relation is empty. - One snapshot. The referenced document is read at the same log position as everything else, so the answer’s
watermarkalso says which version of it the answer read, and the evaluation’srelated_documentsnames that revision. - The reverse lookup from a referenced document to the judged documents that point at it is a reference index on the judged documents’
mineattribute. The first version that needs it builds it as areference_indexjob, listed in the create response’sjob_ids, and the judgment’s answers areunavailableuntil the job is done. It counts toward the 3 reference indexes a namespace can have, and an attribute used in both directions is one index. - Bands.
{"path": "state.score", "bands": [0.3, 0.7], "labels": ["low", "medium", "high"]}shows the label of the band a number falls in, not the number: 0.29 islow, 0.3 ismedium, and 0.7 or more ishigh. Bands work in any relation. See bands. confirm. Creating a version withrelatedon a judgment that runson_changereturns a replay estimate and creates nothing until you sendconfirm: true.
Fan-out
A write to a judged document re-judges it, as always. A write to its referenced document re-judges every judged document that points at it and is inside the judgment’s re-judge scope, but only when the write changes what the relation shows of it. That is the fan-out, and it is what this kind of judgment costs:re-judgments a month = referenced-document changes a month × judged documents per change inside the scopeA referenced document with many judged documents pointing at it can turn one small edit into hundreds of thousands of evaluations. Every control below exists to shrink one of those two factors, or to make a large fan-out visible before it runs.
What a fan-out costs
The worked numbers use one namespace:- 100,000 referenced documents, each pointed at by 200 judged documents on average: 20M judged documents.
- 4% of the judged documents were created in the last 30 days, so 8 per referenced document, and a quarter of those match the scope’s
where(an open status, say), so 2. - Each judged document’s context and question come to about 900 tokens, so 1 judgment unit, at $0.10 per 1,000 units, and less past 100M units a month.
B costs over $50,000 a month. The scope cuts the judged documents per change a hundredfold, and bands cut the changes thirtyfold, which brings B down to the size of A.
Debounce is what keeps a much more volatile field bounded. A score rewritten every minute is 43,200 changes a month for each referenced document. The 10-minute fan-out debounce never settles on it, so the 1-hour ceiling fans it out once an hour for as long as it keeps changing, 720 times a month. With bands on top, only a change of band counts: a band change that settles fans out once, and a band that keeps flipping still fans out at least once an hour.
One large referenced document, pointed at by 200,000 judged documents, fans out to all of them on one change: $20, and more than a day at the default share of 2 engine requests, about 2 documents a second. That is above
job_above, so it waits for you to confirm its estimate. With the 30-day scope and where it is about 2,000, which runs on its own in under 20 minutes.
Judged documents outside the scope cost nothing, and a re-judged document whose context did not change is not billed.
The controls, in order of effect
Every default is a setting you change with aPATCH and no new version. The judgment’s are in freshness.fanout; job_above and confirm_jobs fall back to the namespace’s, and share is the namespace’s only.
The fan-out debounce is separate from the judgment’s own
debounce_ms, which still governs the judged documents’ own writes: a new judged document is judged within seconds, while changes to its referenced document wait 10 minutes. The scope’s age counts from the change, not from now, so one change’s scope stays fixed however long its fan-out waits. A document’s age is from its created_at: when it was first written to Vainona, or the creation time that write gave. Import judged documents with their original created_at, so only the recent ones fall inside the scope; imported without it, every one of them is inside the default scope for 30 days.
PATCH like this one changes those two keys and keeps the others, scope.where included. null for job_above or confirm_jobs drops the judgment’s own value, so it follows the namespace’s again. GET returns every value in effect, and fanout_sources says where each comes from: judgment, namespace or default. Widening the scope, raising job_above or turning confirm_jobs off needs no confirm and can raise the bill a lot; the namespace budget still caps what is spent.
Fan-out when a referenced document changes lists each setting with its bounds.
Fan-out jobs and the daily limit
A fan-out that would re-judge more thanjob_above judged documents is a fanout job, visible under /jobs and in the dashboard like a backfill:
- The estimate counts the judged documents in scope whose answers are older than the change, and prices them like a backfill.
duration_sis at the namespace’s share of engine requests, about 2 documents a second at the default share, which is why it is hours for a large one. - Confirm. The job waits in
awaiting_confirmuntilPOST /jobs/{id}/confirm, unlessconfirm_jobsisfalse, when it starts at once. Confirming is refused withbudget_exceededwhen the estimate does not fit what is left of the namespace’s monthly budget, and a job that would start on its own but does not fit waits instead. - One job per referenced document. Another change while the job waits moves
referenced.revisionto it and refreshes the estimate, so one confirm covers every change so far. - Cancel leaves the answers as they are. The ones in scope read
staleuntil the judged document or the referenced document next changes.pauseandresumework as for a backfill. - The daily limit. A fan-out at or below
job_aboveruns on its own, with no job, and its spend shows in the namespace’s spend.auto_daily_limitcaps how many judged documents those automatic fan-outs re-judge in any 24 hours. A fan-out that would pass it becomes afanoutjob that waits for confirm, however small, and even whenconfirm_jobsisfalse.nullmeans no limit. - Background work. Fan-out runs behind the namespace’s own changes, like a backfill, on at most
shareof its in-flight engine requests (2 of 4 by default). A budget pause stops it like all judging.
What the answers show
- In scope: an answer is
pendingfrom the change until the fan-out re-judges it, including while its job waits for confirm, andfreshagain once an answer lands with a watermark at or above the change. If the job is cancelled, it isstale. - Outside the scope: the answer keeps its freshness and is not marked
pending. Itswatermarkand its evaluation’srelated_documentssay which version of the referenced document it read, and a referenced document whoserevisionis above the watermark shows it read an earlier one. On a get, the answer also listsreferenced_changes: the newest write to each document it points at that changed what the relation shows, with itsrevisionand time. The judged document’s next own write re-judges it against the referenced document as it is then. The dashboard’s document view says which of these applies. - Only
on_changefans out. Underon_read,periodicandmanual, a change makes the in-scope answersstale, as any related write does. updated_atchanges on every write. A relation that shows a referenced document’supdated_atfans out on every write to it, so create warns about it.
What the replay estimate cannot count
Creating a version withrelated on a judgment that runs on_change, or switching such a judgment to on_change, returns the replay estimate of its monthly cost and does nothing until you send confirm: true. It cannot count fan-out: storage keeps only a referenced document’s newest version, so the replay cannot tell which of its past writes changed what the relation shows. For this kind of judgment it returns "excludes": ["fanout"] and lower_bound: true, and the dashboard shows the figures as “at least, not counting fan-out”. The replay still counts the judged documents’ own writes and every relation that reads documents pointing at them. Each fan-out above job_above shows its own estimate before it runs.
Namespace fan-out settings
PATCH /namespaces/{ns} sets how fan-out runs for every judgment in the namespace:
shareis the most of the namespace’s in-flight engine requests fan-out may use: 0.5 by default, always at least one request, so the rest serve its ordinary judging. It is above 0 and at most 1.job_above(10,000) andconfirm_jobs(true) are the values its judgments follow unless they set their own.
PATCH, and GET returns the values in effect. See namespaces.
Not available yet
- Counting fan-out in the replay estimate.
- Limits on a namespace’s total fan-out other than its budget and each judgment’s daily limit.