oleander-whirlwind
10/15/2025, 6:14 AMMarvin
10/15/2025, 6:14 AMMarvin
10/15/2025, 6:18 AMsubmit and `map`:
- Fan out via deployments (launch many child flow runs)
- From a parent flow, trigger N child runs with run_deployment. This scales across workers/work pools and doesn’t tie all work to a single flow run’s resources.
- Docs: How to: run deployments, API: API ref
- Example:
from prefect import flow, get_run_logger
from prefect.deployments import run_deployment
from prefect.flow_runs import wait_for_flow_run
@flow
def parent(items):
logger = get_run_logger()
runs = [
run_deployment(
name="child-flow/my-deployment",
parameters={"item": item},
as_subflow=True # set False to detach from the parent
)
for item in items
]
# optionally wait for completion
for fr in runs:
<http://logger.info|logger.info>(f"Waiting for {fr.name} ({fr.id})")
wait_for_flow_run(fr.id)
- Fan out across a distributed task runner (Dask/Ray)
- Use Prefect’s Dask or Ray integrations to spread work across a cluster. Your call pattern can stay simple (loop + submit or mapping), but execution fans out to the cluster.
- Dask: Prefect Dask integration
- Ray: Prefect Ray integration
- Fan out using subflows (without deployments)
- If each unit of work is itself a flow, call the child flow multiple times from the parent. For large scale-out, prefer the deployment approach above so runs can be distributed to workers.
- Fan out inside a single task/flow using native concurrency
- For I/O-heavy work that doesn’t need per-item task visibility, you can parallelize inside a single task or flow using asyncio.gather or thread/process pools. This is simple but trades away Prefect-level visibility per item.
- Orchestration-level fan out via work pools/work queues
- Combine the deployment approach with work pools to scale horizontally and control concurrency centrally.
- Concepts: Work pools overview
Nice-to-have utilities when fanning in results:
- prefect.futures.as_completed and prefect.futures.wait let you consume results as they finish or wait on groups.
Quick question to steer you better:
- Do you want to fan out within a single flow run (same process/machine), or spawn many separate flow runs that workers can pick up across your infrastructure?oleander-whirlwind
10/15/2025, 6:29 AMMarvin
10/15/2025, 6:29 AMMarvin
10/15/2025, 6:31 AMmy_task.submit(...) in a loop or use my_task.map(iterable=...) to spawn many task runs within a single flow run.
- How it’s tracked: Each item becomes its own Task Run with its own state, logs, retries, caching, duration, etc., under the same parent Flow Run.
- When to use: You want per-item task visibility and retries, but all work runs on the same execution environment (unless you’re using a distributed task runner).
- Docs: Task runners concept (covers .submit and mapping)
2) Subflow fan out (call flows from a flow) — tracked as subflow runs
- What it is: Call another @flow multiple times from a parent flow.
- How it’s tracked: Each call appears as a Subflow Run under the parent Flow Run with its own state, logs, and timings. By default, subflows share the parent’s process/executor unless you explicitly run deployments.
- When to use: You want hierarchical structure and clear ownership between components, but do not need to scale each unit independently across your infrastructure.
- Docs: Flows and subflows
3) Deployment-based fan out (launch many flow runs) — tracked as separate flow runs
- What it is: From a parent flow, call run_deployment(...) many times to create child Flow Runs that workers pick up from work pools/queues.
- How it’s tracked: Each item is a full Flow Run. If as_subflow=True, they appear as children of the parent; if False, they are detached runs. Each run has independent infra, scaling, retries, and concurrency control.
- When to use: Large scale-out across multiple machines or environments, strong isolation per item, and central orchestration controls (concurrency limits, pools, queues).
- How-to and API: Run deployments, API reference
4) Distributed task runners (Dask/Ray) — tracked as task runs, executed on a cluster
- What it is: Use Prefect’s Dask or Ray integration so your `submit`/`map` fan-out runs on a distributed cluster.
- How it’s tracked: Still individual Task Runs in the parent Flow Run, but scheduled/executed on the cluster for scale/performance.
- When to use: You want the convenience of task-level fan out (per-item retries/visibility) with distributed execution.
- Docs: Prefect Dask, Prefect Ray
5) In-task concurrency (asyncio, threads, processes) — not tracked per unit of work
- What it is: Inside a single task or flow, use asyncio.gather, concurrent.futures, or other concurrency to parallelize.
- How it’s tracked: Only the single task/flow run is tracked; individual units are not visible as separate runs. No per-item retries/caching/lineage.
- When to use: Lightweight I/O concurrency where you don’t need per-item observability or retries.
Helpful utilities when fanning in results
- prefect.futures.as_completed and prefect.futures.wait help manage groups of Prefect futures. API ref: Prefect futures
Rule of thumb
- Need per-item visibility and retries within one flow run? Use task fan out (`submit`/`map`), optionally with Dask/Ray for scale.
- Need hierarchical separation but staying in-process? Use subflows.
- Need to scale each item independently across infra, with work pools/queues and isolation? Use deployment-based fan out (run_deployment).
If you share your target scale and infrastructure (single machine vs. cluster vs. workers), I can recommend the cleanest pattern and a concrete example.oleander-whirlwind
10/15/2025, 6:59 AMMarvin
10/15/2025, 7:00 AMMarvin
10/15/2025, 7:01 AMfrom prefect import flow, task
@task
def square(x):
return x * x
@flow
def mapped():
nums = [1, 2, 3, 4]
futures = square.map(nums) # fan out to task runs
results = [f.result() for f in futures] # fan in results
print(results)
if __name__ == "__main__":
mapped()
Minimal submit example
from prefect import flow, task
@task
def square(x):
return x * x
@flow
def submitted():
nums = [1, 2, 3, 4]
futures = [square.submit(n) for n in nums] # fan out to task runs
results = [f.result() for f in futures] # fan in results
print(results)
if __name__ == "__main__":
submitted()
Notes:
- Both patterns create separate Task Runs you can observe in the UI.
- Docs (task runners, submit/map): Task runners conceptoleander-whirlwind
10/15/2025, 7:03 AMMarvin
10/15/2025, 7:03 AMMarvin
10/15/2025, 7:03 AMfrom prefect import flow, task
nums = [1, 2, 3, 4]
@task
def square(x):
return x * x
@flow
def mapped():
futures = square.map(nums) # fan out
results = [f.result() for f in futures] # fan in
print(results)
@flow
def submitted():
futures = [square.submit(n) for n in nums] # fan out
results = [f.result() for f in futures] # fan in
print(results)
if __name__ == "__main__":
mapped()
submitted()
Tip: if you want results as they complete, you can iterate with prefect.futures.as_completed(...). For background on how these run concurrently (and how to scale with different runners), see Task runners concept.