Durable execution
How does logging every effect let one program survive crashes, and when is it better than a DAG?
This is this mechanism's real alternative architecture, not a feature. It is worth deriving from the beginning, because it reverses the starting assumption.
Two ways to externalize state
The DAG model externalizes state by decomposing the work. Each task is a separate process, and the state between tasks lives in storage the orchestrator does not read.
Durable execution keeps the work as one program and externalizes state by logging effects. Every call to an external system (an activity) has its result appended to a durable log. When the process dies, a new process replays the function from the start. Every logged call returns its recorded result instead of running again. The program arrives at exactly the position it had, with its local variables, loops and conditions intact.
def refund_workflow(request): # runs as ordinary code
ok = activity(validate, request) # result is logged the first time
appr = wait_for_signal("approved") # durable wait: a row, not a process
ref = activity(issue_refund, request)
try:
activity(notify_customer, request)
except Failure:
activity(reverse_refund, ref) # compensation is just another activity
# After a crash: replay from the top. validate/issue_refund return logged results
# without running again; execution continues at the first activity not yet in the log.This is redo recovery (replaying a log to rebuild state after a crash) applied to program control flow instead of database pages. A write-ahead log appears three times in this mechanism: the metadata database, the run history and the activity log. It is the clearest sign that durability has exactly one implementation.
The strict constraint: replay must be deterministic
If the function branches on now(), a random value or a direct network call, the replay takes a different path than the original, and the logged results no longer match the calls being made. So the runtime must intercept all nondeterminism: time, randomness, I/O and concurrency. It routes each of them through the log. This is the same requirement as the ban on now() in task bodies (Safe re-execution), but enforced by the runtime instead of by convention. That difference is the main thing you buy.
When to use which
| DAG of tasks | Durable execution | |
|---|---|---|
| State between steps | In external storage; the orchestrator cannot see it | In the workflow's own variables, logged |
| Unit of retry | The task (coarse, chosen by the author) | The activity (fine, natural) |
| Control flow | Static graph; loops and conditions are awkward | Ordinary code |
| Step granularity | Minutes to hours; launch cost forbids smaller | Milliseconds; no process per step |
| Natural workload | Bulk data transformation | Long-lived business processes, sagas, approvals |
| Versioning | Silent semantic drift (Safe re-execution) | Explicit divergence that must be handled in code |
| Visibility | The graph can be inspected before running | The shape is known only by running |
A saga is a long business transaction made of steps that each have a compensating action (an action that undoes the effect: refund reverses a charge). If a later step fails, the earlier steps are compensated. Durable execution makes sagas practical, because compensations are just more activities in the log.
The clean dividing line:
- Durable execution is better where state between steps is small and control flow is complicated: order fulfillment, multi-step provisioning, sagas, anything with a human in it.
- The DAG model is better where state between steps is enormous and control flow is trivial. That is most data engineering: the state is a hundred-gigabyte table that cannot live in the workflow's variables, and the graph is a straight line.
Neither replaces the other. They solve problems that look similar only because both draw boxes and arrows.
Log every effect and replay the program to recover its position — a WAL for control flow, which buys fine-grained retry and real branching at the cost of enforced determinism, and which is right when the inter-step state is small.