Multiple concurrency limits per task

Cap each tenant and the whole task at the same time, share one limit across many tasks with concurrencyLimit(), and change any limit at runtime without a deploy.

Matt Aitken

Matt Aitken

CEO, Trigger.dev

One task can now hold several concurrency limits at once. Set { perKey: 1, total: 10 } and every tenant runs one at a time while the task as a whole never passes 10. Add a named limit and that same run also draws from a pool shared across tasks. A run only starts when every limit it holds has room, and takes a slot in each while it runs.

Per-tenant and total, on one task

perKey caps each concurrencyKey pool. total caps every run of the task together, keyed or not. Set both and each tenant gets at most perKey while the whole task never passes total:


import { task } from "@trigger.dev/sdk";
export const generateReport = task({
id: "generate-report",
// each user runs at most 1 at a time, and at most 10 run in total
concurrency: { perKey: 1, total: 10 },
run: async (payload) => {
// ...
},
});

Share a limit across tasks

Declare a named limit with concurrencyLimit() and every task that holds it draws from the same pool. Handy when 3 different tasks all call the same rate-limited API:


import { concurrencyLimit, task } from "@trigger.dev/sdk";
// at most 25 concurrent runs across every task that holds this limit
export const openaiLimit = concurrencyLimit({ name: "openai", total: 25 });
export const summarizeThread = task({
id: "summarize-thread",
// this task runs at most 5 at once, and also counts towards the openai limit
concurrency: [{ total: 5 }, openaiLimit],
run: async (payload) => {
// ...
},
});

A task can hold one inline limit plus up to 2 named limits. A run only starts when every limit it holds has room, and it takes a slot in each while it executes.

Cap a tenant across every task

A named limit's perKey follows each run's own concurrencyKey. So one declaration caps a tenant across everything they trigger, while each task keeps its own tighter cap:


import { concurrencyLimit, task } from "@trigger.dev/sdk";
// each tenant runs at most 10 at once across every task that holds this limit
export const tenantLimit = concurrencyLimit({ name: "tenant", perKey: 10 });
export const processWebhook = task({
id: "process-webhook",
// webhooks are capped at 2 per tenant, within the tenant's overall 10
concurrency: [{ perKey: 2 }, tenantLimit],
run: async (payload) => {
// ...
},
});
await processWebhook.trigger(payload, { concurrencyKey: tenantId });

Switch limits when you trigger

Pass concurrency at trigger time to swap a run's named limits. The task's inline limit still applies:


// this run counts towards "priority" instead of the task's declared named limits
await generateReport.trigger(data, { concurrency: ["priority"] });

Turn the dial without a deploy

The new concurrencyLimits namespace reads and changes limits at runtime. retrieve() returns each bound plus live running and queued counts:


import { concurrencyLimits } from "@trigger.dev/sdk";
await concurrencyLimits.override("openai", { total: 50 });
await concurrencyLimits.reset("openai"); // back to the values in your code
await concurrencyLimits.pause("openai"); // nothing holding it can start
await concurrencyLimits.resume("openai");

Overrides survive deploys until you reset them. Pausing keeps your configured bounds, so resuming puts everything back as it was. The same controls are on the Concurrency page in the dashboard and in the management API.

Upgrading

Everything here needs @trigger.dev/sdk 4.7.0 or later. The queue-level concurrencyLimit option keeps working, and is now deprecated in favor of concurrency.

queues.overrideConcurrencyLimit() and queues.resetConcurrencyLimit() are deprecated too: a task's inline limit is addressable as task/<task-id>, so concurrencyLimits.override("task/my-task", { total: 10 }) does the same job. Pausing a queue with queues.pause() is unchanged.

Every pattern, from per-tenant caps to a global cap on a shared API, is in the concurrency docs.

Ready to start building?

Build and deploy your first task in 3 minutes.

Get started now