Fan-Out & Fan-In
Split a step's result into parallel children, collect results into a downstream step.
Split a step's result into parallel children, collect results into a downstream step.
Split a step's result into parallel child jobs, then collect all results
into a downstream step. This is the closest analog to Celery's chord
(a group of parallel tasks followed by a callback), but the fan-out size is
derived at runtime from the predecessor's return value instead of being
fixed when the group is built.
"each"The predecessor's return value must be iterable. Each element becomes a separate child job:
@queue.task()
def fetch() -> list[int]:
return [10, 20, 30]
@queue.task()
def process(item: int) -> int:
return item * 2
@queue.task()
def aggregate(results: list[int]) -> int:
return sum(results) # receives [20, 40, 60]
wf = Workflow(name="map_reduce")
wf.step("fetch", fetch)
wf.step("process", process, after="fetch", fan_out="each")
wf.step("aggregate", aggregate, after="process", fan_in="all")Child nodes are named process[0], process[1], process[2] and appear
in status queries.
fetch completes — the tracker reads its return valuedepends_on wait)((results_list,), {}) as its payloadIf the predecessor returns an empty list, the fan-out parent is marked
COMPLETED immediately with zero children, and the fan-in receives an
empty list:
@queue.task()
def fetch() -> list:
return [] # nothing to process
# aggregate receives []Steps after the fan-in work normally:
wf = Workflow(name="full_pipeline")
wf.step("fetch", fetch)
wf.step("process", process, after="fetch", fan_out="each")
wf.step("aggregate", aggregate, after="process", fan_in="all")
wf.step("report", send_report, after="aggregate") # runs after aggregateBy default (on_failure="fail_fast"), if any fan-out child fails:
FAILEDSKIPPEDFAILEDCombine with conditions for more control:
wf.step("handle_error", alert, after="process", condition="on_failure")