Skip to content

13.7 What a rule set costs — quoted before it runs

Derivation spends graph work: rounds of your rule bodies, and a fact written for every row a body binds. That is a different currency from the model calls a producer spends, and it is priced separately.

Before any rule evaluates, the engine counts each body on its first round — counting and discarding, so nothing is materialized and nothing is written. Two budgets are checked:

Budget Default What is counted
facts 50,000 the rows the whole body binds — the facts it would conclude
work 1,000,000 the rows the body examines before its first WITH that aggregates (count, sum, collect, DISTINCT, ORDER BY) or filters (WITH … WHERE)
deadline the query’s own how long any one counting statement may run before the database stops it: by default, the time the query has left to run inline (its query timeout), so pricing is never a tighter wall than the query already has

Each count stops one row past its budget, so pricing a body never costs more than the budget it is priced against — a cartesian of a billion rows is priced in the time it takes to stream a million. The work count exists because an aggregating body can conclude a handful of facts from ruinous work: pair every verse of a corpus with every other, count the people each pair shares, keep the pairs that share six. Its facts are few; the rows it must examine to find them are the square of the corpus.

Past either budget the query is refused with the count, the budget, and which rule accounts for it:

TOO_EXPENSIVE: deriving :CO_LOCATED would bind at least 50001 row(s) on the first
round alone (gate: 50000). Rule 1 of 'probe/cartesian' accounts for 50001 of them —
an under-constrained body is quadratic in the nodes it binds, so narrow it (anchor one
side, add a filter) or raise the rule set's own limits deliberately if the estate is
genuinely that dense.

Why the cut falls before a filter as well as an aggregation: a count stops at rows that reach it, so behind a selective WITH … WHERE it measures output, not work — finding a million rows that pass can mean examining most of a cartesian. A filter in a MATCH’s own WHERE cannot be cut away without pricing a different query (an anchoring equality there is what makes a body cheap), and that is what the deadline is for.

Slow is not the same as too expensive. A derivation that takes fifteen seconds of a query’s three-minute allowance is entitled to run inline; what is refused is a count that cannot finish within the whole allowance, because the evaluation after it would need at least as long again.

A count that is stopped before it finishes — terminated, timed out, or out of the engine’s own work budget — is treated as evidence of cost, and the query is refused the same way. It is never read as “no quote” and run unpriced. A body the engine cannot count at all (a dialect that rejects the appended clauses) is not quoted, and the ceilings below remain underneath it.

Almost always this means a relationship body that forgot to constrain one side. MATCH (a:Peer), (b:Peer) binds every peer against every other; anchoring one side to the other through a real relationship, or filtering it, is the fix.

Expensive is not the same as forbidden. Asked through a surface that supports it, an over-budget query is parked rather than refused outright, and you are offered the same four choices any expensive query gets: proceed, narrow it first, run it in the background and collect the result later, or cancel. A derivation that genuinely is that large is a scheduling question, and answering “background” is the normal answer to it. What the engine will not do is spend the time without asking.

The count is a lower bound, and deliberately so. A recursive rule reads a label that has no members on the first round, so a transitive closure is under-counted; and every count stops past its budget, so “at least N” is the most a refusal ever claims. The engine can let expensive work through, but it can never refuse cheap work on a guess.

Two ceilings sit underneath as backstops, and you should never meet them:

Ceiling Default What it means
maxPairs 250,000 A relationship set derived more pairs than this. Bounds what reaches the graph; it is checked after a round has bound its rows, so treat it as a last resort rather than a budget.
maxRounds 1,000 The fixpoint did not settle. The message names what was still moving: a derived property climbing (§13.2), or simply an estate deeper than the cap.

Both fail loudly, as BUDGET_EXCEEDED naming the ceiling and its unit (pairs or rounds), and the run is discarded whole: nothing is concluded from it. Neither truncates: you will never receive a derivation that quietly stopped early.