You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: use-case-first structure for queue concurrency
A Use cases index links each goal to its section, the multi-queue
section names the home queue and gate concepts once and gives each
pattern its own worked example (per-tenant cap across tasks, global cap
for a shared resource via a combined-only queue, pinned-key shared
pool), and the per-key-except-combined rule gets a warning callout.
Folds in the simplified wording and removes self-hosting notes.
Copy file name to clipboardExpand all lines: docs/queue-concurrency.mdx
+56-31Lines changed: 56 additions & 31 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,6 +11,16 @@ Controlling concurrency is useful when you have a task that can't be run concurr
11
11
12
12
It's important to note that only actively executing runs count towards concurrency limits. Runs that are delayed or waiting in a queue do not consume concurrency slots until they begin execution.
13
13
14
+
## Use cases
15
+
16
+
-**Limit how many runs of a task execute at once**: [Setting task concurrency](#setting-task-concurrency)
17
+
-**Share one limit across several tasks**: [Sharing concurrency between tasks](#sharing-concurrency-between-tasks)
18
+
-**Give each tenant its own separate concurrency**: [Concurrency keys and per-tenant queuing](#concurrency-keys-and-per-tenant-queuing)
19
+
-**Per-tenant limits with a ceiling on the whole queue**: [Combined concurrency across keys](#combined-concurrency-across-keys)
20
+
-**Cap a tenant across every task they run**: [A per-tenant cap across multiple tasks](#a-per-tenant-cap-across-multiple-tasks)
21
+
-**Cap a shared resource, like an external API, across tasks and tenants**: [A global cap for a shared resource](#a-global-cap-for-a-shared-resource)
22
+
-**Funnel every run into one shared pool**: [One shared pool ignoring keys](#one-shared-pool-ignoring-keys)
23
+
14
24
## Default concurrency
15
25
16
26
By default, all tasks have an unbounded concurrency limit, limited only by the overall concurrency limits of your environment.
The combined limit only applies to runs triggered with a `concurrencyKey`; runs without a key are governed by `concurrencyLimit` alone. On the Queues page in the dashboard, a queue with a combined limit shows it in brackets next to the per-key limit, e.g. `1 (10)`.
177
187
178
-
<Note>
179
-
If you self-host, combined limits are enforced by default and can be disabled with
180
-
`RUN_ENGINE_TOTAL_CONCURRENCY_LIMITS_ENABLED=0`. When enforcement is disabled the limit is
181
-
still accepted, stored, and shown, but runs are not held back by it.
182
-
</Note>
188
+
<Warning>
189
+
On a queue used with `concurrencyKey`, every limit applies per key value except
190
+
`combinedConcurrencyLimit`, which is the only cap that spans the whole queue.
191
+
</Warning>
183
192
184
-
## Holding slots in more than one queue (queue gates)
193
+
## Using multiple queues at once
185
194
186
-
Sometimes one limit isn't enough: each tenant's webhook processing should be capped, but the tenant should also have a global cap across every task they run. Queue gates let a run hold a concurrency slot in more than one queue at once.
195
+
Sometimes one limit isn't enough. A run always waits in one queue, its **home queue**, but it can also hold a concurrency slot in up to two more queues, called **gates**. A run starts only when its home queue and every gate all have capacity, occupies a slot in each while it executes, and releases them together when it finishes or suspends.
187
196
188
-
Pass an array as `queue`: the first entry is the run's home queue (where it waits), and up to two more entries name gates — other queues the run must also have capacity in before it starts, and occupies while it executes:
197
+
Pass an array as `queue`: the first entry is the home queue, the rest name gates. The same array form works when you trigger, replacing the task's gates for that run:
198
+
199
+
```ts
200
+
awaitprocessWebhook.trigger(payload, {
201
+
queue: ["webhooks", "tenant"],
202
+
concurrencyKey: tenantId,
203
+
});
204
+
```
205
+
206
+
### A per-tenant cap across multiple tasks
207
+
208
+
A gate without a `concurrencyKey` uses the run's own key. Declare a shared queue and gate every relevant task on it, and each tenant gets one cap spanning all of those tasks:
189
209
190
210
```ts /trigger/webhooks.ts
211
+
//each tenant runs at most 10 at once across every task that gates on this queue
Because the trigger passes a `concurrencyKey`, the home queue splits per key as usual: `concurrencyLimit: 2` caps each tenant's webhook runs, not the task overall (add a `combinedConcurrencyLimit` to the home queue to bound it across all tenants).
228
+
Because the trigger passes a `concurrencyKey`, the home queue splits per key as usual: `concurrencyLimit: 2` caps each tenant's webhook runs, not the task overall.
208
229
209
-
A gate without a `concurrencyKey` uses the run's own key, so the shared `tenant` queue above caps each tenant across every task that names it as a gate. Give the gate a literal key to pin it to a single slot pool instead, for example capping all traffic to one external provider across your whole environment:
230
+
### A global cap for a shared resource
231
+
232
+
To cap something global, like total traffic to an external API, across many tasks and all tenants: declare a queue with only a `combinedConcurrencyLimit` and gate on it. Tenant keys still split the gate into per-key pools, but with no per-key limit the combined cap is the only constraint:
210
233
211
234
```ts /trigger/sync.ts
212
-
//the gate's capacity comes from the queue's own declaration
0 commit comments