# Flow Concurrency in Kestra: Limit Parallel Runs

Control how many executions of a flow can run at the same time.

The flow-level `concurrency` property lets you limit how many executions of a flow can run concurrently by setting the `limit` key.

Think of concurrency as a global execution limit for that specific flow. The concurrency limit and behavior is then applied to all executions of that flow, regardless of whether those executions have been started automatically via a trigger, webhook, an API call, or manually created from the UI.

:::alert{type="info"}
Concurrency limits executions of a flow, not the number of tasks a worker runs. Task processing is still governed by worker thread pools and task runners. Concurrency uses database locks to hold slots, so heavy contention (many executions fighting for the same lock) can increase database load and slow scheduling.
:::

Use concurrency when you need to protect downstream systems (rate limits, database load, external APIs) or enforce “only one execution at a time” semantics. Do **not** rely on concurrency to throttle Kestra worker usage; adjust worker threads, task runners, or queue sizing for that.

<div class="video-container">
  <iframe src="https://www.youtube.com/embed/lDGOqqMyQEo?si=01KzCswO3dHdhYdt" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div>

For example, if you set the concurrency `limit` to 2, only two executions of that flow will be allowed to run at the same time. If you try to trigger a third execution, it will be queued until one of the two running executions is completed.

### When to use concurrency

- Protect a shared target system (databases, SaaS APIs, warehouses) from overload.
- Enforce sequential processing for stateful workloads (one ETL load at a time).
- Keep a small, fixed number of parallel executions within an external rate limit.

### When **not** to use concurrency

- Throttling worker CPU/RAM usage — tune worker thread pools or task runners instead.
- Replacing task-level limits — use task runner settings (e.g., container resources) and retry/backoff for per-task control.
- Broad platform protection — use platform sizing and queue configuration rather than flow-level concurrency locks.

```yaml
id: concurrency_example
namespace: company.team

concurrency:
  limit: 2

tasks:
  - id: wait
    type: io.kestra.plugin.scripts.shell.Commands
    commands:
      - sleep 10

```

In the UI, the third execution is queued while the first two finish successfully.

![concurrency](./concurrency.png)

## `behavior` property

You can customize the behavior when the concurrency limit is reached by choosing to queue, cancel, or fail the new execution. To do that, set the `behavior` Enum-type property to one of the following values:

- `QUEUE`
- `CANCEL`
- `FAIL`

For example, with `concurrency.limit` set to 2 and `CANCEL` or `FAIL` behavior, the third execution is immediately marked as `CANCELLED` or `FAILED` without running any tasks.

Below is a full flow example that uses the `concurrency` property to limit the number of concurrent executions to 2. The `bash` task sleeps for 10 seconds, so you can trigger multiple executions of that flow and see how the `concurrency` property behaves.

:::alert{type="warning"}
Each execution that waits for a concurrency slot holds a database lock. Large backlogs (many queued executions) can increase lock contention and slow down scheduling. If you expect spikes, combine conservative limits with backoff at the source (e.g., trigger rates) and keep an eye on the Concurrency tab in the UI.
:::

```yaml
id: concurrency_limited_flow
namespace: company.team

concurrency:
  behavior: FAIL # QUEUE, CANCEL or FAIL
  limit: 2 # can be any integer >= 1

tasks:
  - id: wait
    type: io.kestra.plugin.scripts.shell.Commands
    taskRunner:
      type: io.kestra.plugin.core.runner.Process
    commands:
      - sleep 10
```

As you can see in the UI, the third execution failed as the first two executions were still running.

![concurrency_fail](./concurrency_fail.png)

:::alert{type="warning"}
When an execution starts from a [Trigger](../07.triggers/index.mdx), the trigger locks until it finishes, preventing multiple executions from that trigger from running concurrently. This means the `behavior` property will not come into effect and instead no new executions will be started.

Read more in the [Locked Triggers](../07.triggers/index.mdx#locked-triggers) section.
:::

## Tracking concurrency slots from the UI

The `Concurrency` tab on the `Flow` page lets you track and troubleshoot concurrency issues. It shows a progress bar with the number of active slots compared to the total slots available. Below that progress bar, you can see a table showing currently running and queued Executions, providing a clear overview of the flow's concurrency status.

![concurrency_page_1](./concurrency_page_1.png)

To see the concurrency behavior in action, you can configure a flow with a concurrency limit as follows:

```yaml
id: concurrent
namespace: company.team

concurrency:
  behavior: QUEUE
  limit: 5

tasks:
  - id: long_running_task
    type: io.kestra.plugin.scripts.shell.Commands
    commands:
      - sleep 90
    taskRunner:
      type: io.kestra.plugin.core.runner.Process
```

Next, trigger multiple Executions of that flow and watch the `Concurrency` tab showing the active slots and queued Executions.

![concurrency_page_2](./concurrency_page_2.png)

## Concurrent executions for Triggers

Any [Trigger](../../05.workflow-components/07.triggers/index.mdx) type supports concurrent executions, allowing multiple instances of the same workflow to run simultaneously. This enables more flexible and scalable workflow patterns.

For example, consider a workflow that takes 60 seconds to complete but is triggered every second. By default, with `allowConcurrent: false`, only one execution can run at a time. If a trigger fires while a previous execution is still running, the new execution will be skipped:

```yaml
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
```

In this example, even though the [Schedule trigger](../../05.workflow-components/07.triggers/01.schedule-trigger/index.md) fires every second, only one execution will run at a time. Setting `allowConcurrent: true` would allow multiple executions to run simultaneously.

## How to troubleshoot Concurrency issues

Imagine that you encounter a situation where the concurrency limit is reached, and some executions are stuck in the `QUEUED` state. Here are some steps to troubleshoot and resolve the issue.

### Check the Concurrency tab

The `Concurrency` tab on the `Flow` UI page described above allows you to see which executions are `RUNNING` and which are `QUEUED` (i.e., waiting or stuck). This page can help you troubleshoot which Executions are taking concurrency slots and which are waiting to be processed.

In the future, this page will also let you run stuck executions while ignoring concurrency limits.

### Edit the Concurrency property

You can edit the `concurrency` property within the flow (or remove that property entirely to get rid of any limits) and `Save` the flow code. The modified concurrency limit and behavior will be immediately taken into account for all Executions in progress because the Executor checks this for the latest flow revision rather than for the revision of the Execution.

:::alert{type="warning"}
Do **not** delete executions, as this makes the issue worse — deleted executions still occupy concurrency slots indefinitely. You can select stuck Executions and hit the `Kill` button to cancel them and free up the concurrency slots, but do not delete them.
:::