The two directions of fan-out
A change can be to the primary itself or to something it embeds.- Primary change (an
ordersrow, aProductnode): recompute that one document. Easy. - Embedded change (a
customerthe order references, aRegiontwo hops from the product): recompute every primary that embeds it. This is the fan-out, and it can touch many documents from one change.
Bounded by the spec
Fan-out only follows paths the spec declares, up tofan_out_max_hops.
A node whose label the spec never traverses produces zero
recomputations — the change event is read and discarded. This is what
keeps a single change from rippling across the whole graph.
Changing a shared lookup node must recompute every primary document that embeds
that value. This can be expensive, but it is required for the target documents
to converge.
The hot-endpoint problem
Now consider a shared lookup node — like aCategory that thousands
of products point at. In the validation graph there was a single shared
status node referenced by ~99,000 documents through one relationship type.
When a single document’s edge to that shared node changes, the naive
fan-out anchors on the relationship’s endpoints — including the shared
node — and asks “which documents reach this node?” The answer is all
99,000, even though only one document’s edge actually changed.
Without handling, a single edge change triggered a 59,498-document
recompute in testing. A 1,000-operation burst queued ~5,800 such
cascades — hours of wasted work for changes that should each touch one
document.
Hot-endpoint detection
At startup, the engine probes each projection path’s leaf cardinality. Any endpoint label below a threshold (default 100, viaVS_NEO4J_HOT_NODE_THRESHOLD) is flagged “hot” and its element IDs are
captured in a small in-memory set.
At event time, relationship events involving a hot endpoint have that
endpoint filtered out of the fan-out anchor set — so only the
primary-side key reaches the recompute path:
What is not filtered
The filter applies to relationship events only. A node-property change on a hot node — say, renaming that shared status node from “Published” to “Live” — still cascades to all ~99,000 documents, because the embedded value genuinely changed for all of them. That’s correct:
The filter removes spurious fan-out (an edge change masquerading as a
graph-wide change), never legitimate fan-out (a shared value actually
changing).
Cost
- Startup: one count query for each analysable projection-path prefix.
- Memory: a set of detected endpoint IDs, keyed by relationship type.
- Per event: relationship-type and endpoint membership lookups.
VS_NEO4J_HOT_NODE_THRESHOLD=0 to disable detection entirely (the
old behavior). Raise it if a genuine fan-out target sits just above the
default and you’d rather treat it as hot.