ReactiveDag.CascadeWorker (reactive_dag v0.17.0-rc.65)

Copy Markdown View Source

The Oban job that propagates ONE change — the entry point for everything a host writes.

A write records what it changed and enqueues this. The job then walks the graph from that change, in its own transaction, until the cascade completes or reaches something that has to stop.

Why the write does not cascade inline

The design says a cascade runs in one transaction. It does — its own, not the writer's. A host write that transitively touches fourteen cells should not hold a user's request open for all of them, and running the walk inside the writer's transaction would put that transaction at risk of exactly the problem this redesign exists to remove: a long-held connection.

So the write's transaction contains one INSERT — this job — and commits. The consequence, worth stating because it surprises: a host reading a derived table immediately after its own write sees the old value. That was true of the drain too, and remains true here.

Uniqueness is a 60-second window, NOT :infinity

This is the trap in this file, and it is worth reading before copying the config from anywhere else.

DrainWorker used period: :infinity with empty args, and that was right for it: every drain did identical global work, so a burst of writes wanted exactly one pending drain.

These args name a SPECIFIC change. :infinity would mean "the same row changing twice, ever, cascades once" — a durable correctness bug, silent, and impossible to notice from the outside. Sixty seconds coalesces a burst of writes to one row and nothing more.

Contrast ReactiveDag.ResumptionWorker, whose :infinity IS right, for a reason that only applies there: its args are a stopping point rather than a change.

Summary

Functions

Enqueue a cascade from one cell's changed keys.

Functions

enqueue(cell, keys, opts \\ [])

@spec enqueue(String.t(), [String.t()], keyword()) ::
  {:ok, Oban.Job.t()} | {:error, term()}

Enqueue a cascade from one cell's changed keys.

Safe to call inside a transaction: it is one INSERT, and it commits with the write that caused it — so a rolled-back write leaves no cascade, and a committed one always leaves exactly one.

versions maps a changed key to the id of the version recording what the change did. Omitting it is allowed and costly: a suspension downstream will carry "*" and resume by recomputing the whole cell.