<@ULVA73B9P>, How does concurrency limit correlate...
# ask-marvin
a
@Marvin, How does concurrency limit correlate to process spawn in the task runner in prefect 3.2
m
thought for 128.3 seconds
Short answer: they don’t. In Prefect 3.2, concurrency limits and process spawning are independent knobs. - Task runner parallelism (max_workers) controls how many tasks a single flow run can execute in parallel and whether that parallelism uses threads or OS processes: - ThreadPoolTaskRunner (alias ConcurrentTaskRunner) → threads only (default) - ProcessPoolTaskRunner → OS processes; spawns up to max_workers processes per flow run - DaskTaskRunner / RayTaskRunner → managed by those frameworks; typically multi-process - Prefect Cloud concurrency limits are “gates,” not resource creators. They decide whether a flow run or task run is allowed to start; they do not spawn threads or processes: - Work pool concurrency limit → caps concurrent flow runs in a work pool - Deployment concurrency limit → caps concurrent flow runs for a deployment - Tag-based task concurrency → caps concurrent task runs by tag across all flows/deployments - Global (named) concurrency/rate limits → general-purpose slots you can acquire in code The effective behavior is the intersection of both: - Within a single flow run, the task runner can schedule up to max_workers tasks in parallel. - Each task still must acquire any required Cloud concurrency slots (deployment/work pool/task-tag/global). If a slot isn’t available, that task won’t start yet. When the slot is granted, the task runs in whatever execution model the task runner uses (thread vs process). Examples - Threads by default:
Copy code
from prefect import flow
from prefect.task_runners import ThreadPoolTaskRunner

@flow(task_runner=ThreadPoolTaskRunner(max_workers=8))
def my_flow():
    ...
- Processes (explicit):
Copy code
from prefect import flow
from prefect.task_runners import ProcessPoolTaskRunner

@flow(task_runner=ProcessPoolTaskRunner(max_workers=4))
def my_flow():
    ...
Rule of thumb - Changing a Cloud concurrency limit will never create more threads/processes. - Changing the task runner or its max_workers will never bypass Cloud concurrency limits. - For a tagged task, the number actually running at once is bounded by both the task runner’s parallelism and the relevant limit (e.g., tag limit). If you can share which limit you’re tuning (work pool, deployment, task tag, or a named/global limit) and which task runner you’re using, I can help you pick the right settings and expected “max parallel tasks” for your case.
a
I want the response in the context of prefect open source
@Marvin I want the response in the context of prefect open source
m
thought for 161.7 seconds
Great callout. Here’s the Prefect 3.x open source view: - They’re independent. Server-side concurrency limits gate whether a run may start; they do not create threads or processes. The task runner alone determines threads vs processes. - ThreadPoolTaskRunner (aka ConcurrentTaskRunner) → threads (default) - ProcessPoolTaskRunner → OS processes - Dask/Ray (optional integrations) → managed by those frameworks, typically multi-process - What’s available in OSS (self-hosted server) and how it interacts: - Work pool concurrency limit → caps concurrent flow runs per work pool (server-enforced) - Deployment concurrency limit → caps flow runs per deployment (server-enforced) - Tag-based task concurrency → caps concurrent task runs by tag across all flows (server-enforced) - Global concurrency/rate limits → general-purpose named limits you can acquire in code (server-backed) - None of the above spawn processes; they only decide when a run may start. The runner then executes in threads or processes based on your choice. - Practical effect - Within one flow run, max parallel tasks = min(task runner max_workers, any applicable tag/global limit slots currently available) - Across flows, work pool and deployment limits cap how many flow runs can be active at once; they don’t change how many threads/processes each flow uses. Examples - Default (threads):
Copy code
from prefect import flow
from prefect.task_runners import ThreadPoolTaskRunner

@flow(task_runner=ThreadPoolTaskRunner(max_workers=8))
def my_flow():
    ...
- Processes (explicit):
Copy code
from prefect import flow
from prefect.task_runners import ProcessPoolTaskRunner

@flow(task_runner=ProcessPoolTaskRunner(max_workers=4))
def my_flow():
    ...
Notes - Concurrency limits are enforced when your flows/tasks are orchestrated by your OSS Prefect server. Pure “local runs” that don’t talk to the server won’t use server-side limits. - Choosing processes vs threads is a performance choice (CPU-bound → processes, IO-bound → threads), not a concurrency-limits concern. Docs - Task runners: Task runners - Work pools: Work pools - Deployments (concurrency): Deployments - Tag-based task limits: Tag-based task concurrency - Global limits: Global concurrency limits If you share which runner you’re using and which limit (work pool, deployment, tag, or global) you’re tuning, I can help compute your expected “max parallel tasks” in OSS.