Liliya Angelova
11/17/2025, 3:41 PMMarvin
11/17/2025, 3:41 PMMarvin
11/17/2025, 3:52 PMrunner.process_limit) and applies when you run flows via flow.serve() or prefect serve (i.e., a local Runner pulling runs from the API).
- Set it via env var or in your profile/config:
export PREFECT_RUNNER_PROCESS_LIMIT=10
or in prefect.toml
[runner]
process_limit = 10
- Docs: RunnerSettings and Settings reference
- What counts as a “process”
- Each flow run the Runner executes is started in its own OS process (via Python multiprocessing). This isolates flow runs from each other.
- Guide: Run flows in local processes
- Multiple flows and many submitted tasks
- If you serve multiple flows with one Runner, the process limit is shared across them. Example: with PREFECT_RUNNER_PROCESS_LIMIT=3, at most three flow runs (in total) execute concurrently; additional runs wait until a slot frees up.
- The limit does not control task-level concurrency. Within each running flow, task concurrency is governed by the flow’s task runner:
- Default is ThreadPoolTaskRunner, which uses threads.
- Configure via the flow’s task_runner=... or the env var PREFECT_TASK_RUNNER_THREAD_POOL_MAX_WORKERS for the default runner.
- Docs: Task runners concept and Task runners API
Concrete example:
- Suppose you serve two flows, each flow run submits 100 tasks with task.submit(...).
- With PREFECT_RUNNER_PROCESS_LIMIT=4, up to 4 flow runs (across both flows) execute at once.
- Inside each running flow, the number of tasks running concurrently is controlled by the task runner (e.g., ThreadPoolTaskRunner(max_workers=20)), independent of the process_limit.
If you share how you’re starting your flows (e.g., flow.serve() in one process vs multiple Runners) and what task runner you use, I can suggest tuned settings.Liliya Angelova
11/17/2025, 4:01 PMMarvin
11/17/2025, 4:01 PMMarvin
11/17/2025, 4:08 PMflow.serve() / prefect serve. In a work-pool + worker architecture, ignore this setting.
- Docs: RunnerSettings
What actually controls concurrency in your case:
- Work pool concurrency limit (server-side, shared across all workers in the pool)
- Set a global cap on concurrent flow runs across the pool:
prefect work-pool set-concurrency-limit <your-pool> <N>
- Docs: Work pools
- Per-worker concurrency (client-side, each worker instance)
- Start each worker with a per-worker cap:
prefect worker start --pool <your-pool> --limit <M>
- If --limit is not provided, the worker has no internal cap and will try to start as many runs as the server will give it (bounded by the pool limit and the VM’s resources).
- Worker docs: Workers, ProcessWorker API: ProcessWorker
What counts as a “process” here:
- Each flow run started by a ProcessWorker is executed in its own OS subprocess. With 3 workers, each with --limit M, you can have up to sum(M) subprocesses on the VM at once (still bounded by the pool’s concurrency limit).
What happens when many events trigger flows at the same time:
- The Prefect server queues the runs in your process work pool.
- Your 3 workers pull runs. Each worker starts up to its --limit. The pool’s concurrency limit caps the total across all workers.
- Any overflow stays queued (Scheduled) and is picked up as slots free up. Runs are not dropped.
Task concurrency inside each flow:
- Independent from the above. Each running flow controls task-level concurrency via its task runner (default thread pool).
- For threads, you can set PREFECT_TASK_RUNNER_THREAD_POOL_MAX_WORKERS or pass ThreadPoolTaskRunner(max_workers=...) on the flow.
- Docs: Task runners
A practical way to size things on a single VM:
- Decide a total cap for your VM (e.g., based on vCPU/memory). Say you choose 6.
- Set the pool limit to that total:
prefect work-pool set-concurrency-limit my-process-pool 6
- Start each of your 3 workers with a per-worker cap that sums to the same total, e.g.:
prefect worker start --pool my-process-pool --limit 2
- Optionally tune task-runner max_workers inside each flow based on whether tasks are CPU- or I/O-bound.
If you share the VM’s vCPU count and whether your flows are CPU- or I/O-heavy, I can suggest concrete values for the pool limit, per-worker limit, and per-flow task-runner settings.