ReactiveDag.Node.Changes.MarkDirty (reactive_dag v0.17.0-rc.65)

Copy Markdown View Source

ORIGINATES A CASCADE from a written record, so what depends on it recomputes. Wired automatically by dirties_on and augmented_by — hosts do not add it.

Why a change, not a notifier

The obvious tool is an Ash.Notifier, and it is the wrong one: Ash dispatches notifications after the transaction commits. A cascade enqueued there is not covered by the write's transaction, so a crash between commit and dispatch loses it — and a lost cascade is silent staleness, the failure mode this feature exists to remove.

An after_action hook runs inside the transaction (its counterpart is named before_transaction precisely because that one does not). The enqueue therefore joins the host's transaction:

  • a rolled-back write enqueues nothing, and
  • a committed write always enqueues exactly one cascade.

Both halves matter. Neither is true of a notifier.

The cascade is the JOB'S transaction, not the writer's

What commits with the write is one INSERT — an Oban job naming what changed. The walk itself happens later, in its own transaction.

That is deliberate. A write that transitively touches fourteen cells should not hold a user's request open for all of them, and running the walk inline would put the host's transaction at risk of exactly the problem this design removes: a long-held connection. The visible consequence is that a host reading a derived table immediately after its own write sees the old value — which was equally true of the drain that preceded this.

Cost

One INSERT per write, inside a transaction the host already holds open. Oban's uniqueness collapses a burst of writes to the same row into one pending cascade. A host writing at a rate where even that contends should feed the leaf from a ReactiveDag.Source poll instead — see the sources guide.