Control How Many Flow Executions Run in Parallel

For the complete documentation index, see llms.txt. For a full content snapshot, see llms-full.txt. Append .md to any kestra.io/docs/* URL for plain Markdown.

Concurrency limits control how many executions can run at the same time, at the flow, namespace, or tenant level. When a limit is reached, new executions are queued, cancelled, or failed depending on the configured behavior.

Once an execution occupies a concurrency slot, it keeps that slot until it reaches a terminal state. This includes executions in the PAUSED state while they wait for manual approval. For example, with concurrency.limit set to 1, a flow paused by a Pause task blocks all subsequent executions until the paused execution is resumed, killed, or otherwise reaches a terminal state. Resuming the execution continues to use its existing slot, so it cannot exceed the configured limit.

Restarted executions and replays of a failed execution go through the concurrency limit like new executions, so they can be queued, cancelled, or failed.

Configuring concurrency

Flow level

Set concurrency.limit on a flow to cap its parallel executions:

id: concurrency_example
namespace: company.team
concurrency:
limit: 2
tasks:
- id: wait
type: io.kestra.plugin.scripts.shell.Commands
taskRunner:
type: io.kestra.plugin.core.runner.Process
commands:
- sleep 10

With limit: 2, a third execution waits until one of the two running executions completes.

Namespace level

Namespace and tenant limits are available in Enterprise Edition.

Set a concurrency limit on a namespace to cap the total simultaneous executions across all flows within that namespace and its children.

Navigate to Namespaces, open the target namespace, click Edit, and scroll to the Concurrency section. Set the Limit and choose a Behavior (Queue, Cancel, or Fail). The same configuration is also available via the API.

A namespace concurrency limit applies to every flow whose namespace matches or is a child of the configured namespace. For example, a limit on company applies to flows in company, company.team, and company.team.project.

Tenant level

Set a concurrency limit at the tenant level to cap total simultaneous executions across all namespaces in the tenant.

In the Instance Owner console, open the tenant’s Settings, then set the Limit and Behavior in the Concurrency section.

behavior property

By default, executions that exceed the limit are queued. Set behavior to control what happens instead:

  • QUEUE: hold the execution until a slot opens (default).
  • CANCEL: immediately mark the execution as CANCELLED.
  • FAIL: immediately mark the execution as FAILED.

With FAIL, every execution the limit rejects ends FAILED, so alerts on failed executions fire once per rejected execution. A trigger with stopAfter: [FAILED] is disabled by the first execution the limit rejects. Use CANCEL when you expect rejected executions and don’t want them to alert or disable triggers.

Time spent in QUEUED counts toward an SLA MAX_DURATION, so a queued execution can breach its SLA before it starts running.

If you expect execution spikes, combine a conservative limit with backoff at the source (e.g., slower trigger rates) to avoid large queues that increase database lock contention.

id: concurrency_limited_flow
namespace: company.team
concurrency:
behavior: FAIL # QUEUE, CANCEL, or FAIL
limit: 2
tasks:
- id: wait
type: io.kestra.plugin.scripts.shell.Commands
taskRunner:
type: io.kestra.plugin.core.runner.Process
commands:
- sleep 10

Evaluation order

Kestra evaluates concurrency limits from the most specific scope to the most general:

  1. Flow-level concurrency is checked first.
  2. Namespace-level concurrency is checked next, starting from the flow’s own namespace and moving up through each parent namespace to the root.
  3. Tenant-level concurrency is checked last.

The first limit reached defines the behavior for that execution. When a more specific limit is reached, no slot is consumed at higher levels.

For example, if a flow defines limit: 5 with QUEUE and its parent namespace defines limit: 10 with FAIL, a sixth execution of that flow is queued (flow-level limit is reached first). If the flow has no limit of its own but the parent namespace is at its limit of 10, the execution fails.

Monitoring concurrency

Per-flow Concurrency tab

The Concurrency tab on a Flow page shows current slot usage, the configured behavior, and a list of running and queued executions for that flow.

Flow Concurrency tab showing 2 of 2 active slots filled with one execution queued

Concurrency Limits page

The Concurrency Limits page lists every flow, namespace, and tenant concurrency limit configured, along with its live running count. It provides a tenant-wide view of concurrency usage across all scopes.

In the open-source edition, open it from Tenant > Concurrency Limits in the sidebar. In Enterprise Edition, only the Instance Owner can open it: in the Instance Owner console, select the tenant and open Concurrency Limits.

Concurrency Limits page listing two flows with their namespaces and running counts

Concurrent trigger executions

Any Trigger type supports concurrent executions through the allowConcurrent property. By default, allowConcurrent: false: while an execution from the trigger is still running, the trigger stays locked and creates no new execution until that execution ends.

id: sleep_concurrent
namespace: company.team
tasks:
- id: sleep
type: io.kestra.plugin.core.flow.Sleep
duration: PT60S
triggers:
- id: schedule
type: io.kestra.plugin.core.trigger.Schedule
cron: "* * * * * *"
withSeconds: true
allowConcurrent: false

Set allowConcurrent: true to allow multiple executions to run simultaneously from the same trigger.

Updating a concurrency limit

Update a concurrency limit when executions are backing up or hitting the limit unexpectedly. Avoid changing limits routinely; a stable limit is a deliberate resource protection decision.

Edit the concurrency.limit value directly in the flow editor and save. For a namespace or tenant limit, edit the Concurrency section of the namespace or tenant settings.

The new limit applies to new executions without a restart. Executions that are already QUEUED stay queued until a running execution of the flow ends, even when the new limit leaves room for them.

If you remove concurrency from a flow and add it back later, the running count restarts at 0. Executions that were already running are not counted, so more executions than the limit can run until they finish.

Troubleshooting concurrency issues

Check the Concurrency tab

The Concurrency tab on the Flow page shows which executions are running, queued, or failed, and which hold slots versus which are waiting.

Flow Concurrency tab for a FAIL-behavior flow showing 0 of 2 active slots with two failed executions

Reset a stuck running counter

If executions were deleted while running, their concurrency slots can remain occupied indefinitely. On the Concurrency Limits page, click the edit icon next to the running count of the affected flow, set the new count, and save. The edit icon changes the running count, not the limit. You can also set the count with the API: PUT /api/v1/{tenant}/concurrency-limit/{namespace}/{flowId}.

Resetting the count doesn’t start queued executions. They stay QUEUED until a running execution of the flow ends, while new executions can take the freed slots first.

Concurrency Limits counter reset dialog with a warning that changing the counter may allow executions to exceed the limit

Parent executions stuck waiting on queued subflows

If parent executions stay RUNNING on a Subflow task while their subflow executions stay QUEUED, a namespace or tenant limit is deadlocked. Each parent holds a slot that its subflow needs, so no execution in that scope starts until a slot frees up. Executions of unrelated flows in the same scope also stay QUEUED, and the deadlock does not clear on its own.

On the Concurrency Limits page, the running count for the affected namespace or tenant stays at its configured limit, while the number of QUEUED executions in that scope keeps growing on the Executions page.

To resolve the deadlock, open a queued subflow execution and click Force run. A force-run execution starts without taking a concurrency slot. When the subflow completes, its parent completes and releases its slot, and the remaining queued executions start. If the queue does not clear, open a waiting parent execution and click Kill current and subflows to release its slot.

To prevent the deadlock, apply one of these changes:

  • Set the limit higher than the number of parent executions that can wait on subflows at the same time.
  • Set behavior to FAIL or CANCEL. A subflow execution that exceeds the limit then ends immediately instead of queuing, so the problem surfaces as a failed or cancelled execution.
  • Set wait: false on the Subflow task when the parent does not need the subflow’s outputs or final state.
  • For a namespace limit, move the subflows to a namespace that the limit does not cover, such as a sibling namespace. Child namespaces count against the parent namespace’s limit, so moving subflows into a child namespace does not help.

Was this page helpful?