Carry run identity on step-dispatch messages; drop the blocking runs.get from the queued-step prologue (#3457)
* Carry immutable run identity on step-dispatch messages; drop the blocking runs.get from the consumer prologue
Closes #3456. Every queued step execution paid a runs.get round trip
before its step_started claim — one RTT per branch on the TTLS-critical
path, and under a 256-branch fan-out burst the read amplification drove
that read to p90 ~5.1s (durabench parallel sweeps), smearing branch
starts.
The dispatch sites (node dispatch loop, delayed retries, the suspension
handler's resilient publish, and the quickjs engine's queueStepMessage)
now stamp WorkflowInvokePayload.runContext with the fields the consumer
actually needs — deploymentId, specVersion, startedAt, rootRunId — all
immutable for the life of a run and known from the run row the producer
already holds. A consumer that receives it skips the run fetch: the
run-status early exit is enforced by the step_started claim itself
(RunExpired → gone, terminal step → skipped), guardDeployment takes the
carried identity, and only the fan-out's LAST completer fetches the full
run row, lazily, for its inline replay — once per fan-out instead of
once per branch. The deployment-mismatch re-route now also preserves
stepInput/runContext on the re-enqueued payload.
Messages without runContext (older producers) keep the legacy prologue;
messages are deployment-pinned, so mixed handling within one run cannot
occur.
* Address review: terminal-only lazy status gate, terminal-run start fence in local worlds, prologue telemetry, last-completer coverage
- The last completer's lazy runs.get result is now gated on
isTerminalWorkflowRunStatus (with a debug log): a stale 'pending' read
— a run with completed steps has necessarily started — no longer
silently abandons the fan-out's continuation; it falls through to the
inline replay, whose next entity write is fenced server-side if the
run truly ended meanwhile.
- world-local / world-postgres now reject step_started on terminal runs
even when the step row still reads 'running' (a redelivered start a
previous delivery claimed): starting work on a finished run is never
valid, and previously the body re-ran with its outcome unconsumable.
In-flight steps still write their terminal events unchanged. This
closes the adapter gap behind the fetch-free prologue's reliance on
the step_started claim as the run-liveness check, and the prologue
comment now states the contract precisely.
- workflow.step.dispatch_prologue span attribute ('run_context' |
'runs_get') makes fetch-free adoption and the saved round trip
observable during version-skew windows.
- Restated why the eager redelivery re-ensure survives on the
fetch-free path (no run fetch to overlap; still cheaper than the
in-band recovery's failed-start round trip).
- New two-phase fan-out coverage: a real replay emits the queued step
message (asserting the stamped runContext), then its redelivery runs
as the LAST completer — zero reads before the step, exactly one lazy
runs.get, run completed; plus the stale-'pending' fall-through and
the genuinely-terminal skip. N
Nathan Rajlich committed
03455a2979663b0e1acfa0ebecba3c2b77187ae8
Parent: 2eb2fe6
Committed by GitHub <noreply@github.com>
on 9/11/2026, 9:31:15 PM