Limit concurrency
Limit active work across every worker or for each account or resource.
Use durable queue concurrency when several workers or processes must share one capacity limit. Worker concurrency alone controls only the slots in one process.
Bound a queue
Put the queue beside the tasks that use it, for example in src/tasks/sync-account.ts. Set concurrencyLimit on the queue definition used by every runtime:
import { queue, task } from '@runlane/core'
import * as z from 'zod'
const env = z.object({ BILLING_API_URL: z.url() }).parse(process.env)
export const billingQueue = queue({
name: 'billing',
concurrencyLimit: 2,
})
export const syncAccount = task({
id: 'account.sync',
queue: billingQueue,
schema: z.object({ accountId: z.string() }),
concurrencyKey: ({ accountId }) => `account.${accountId}`,
async run({ accountId }, context) {
const response = await fetch(new URL(`/accounts/${encodeURIComponent(accountId)}/sync`, env.BILLING_API_URL), {
method: 'POST',
signal: context.signal,
})
if (!response.ok) throw new Error(`Billing API returned ${response.status}`)
},
})Without a concurrency key, at most two runs occupy this queue's capacity in one Runlane environment. Storage enforces the limit across workers that share the lane.
Partition the limit
Add a task-level key when each customer or resource should get its own capacity partition.
Capacity is partitioned by environment, queue, and concurrency key. Here, each account can run up to two attempts. Different accounts do not consume one another's capacity.
A concurrencyKey requires a queue with concurrencyLimit.
Set local worker fan-out separately
Set process-local fan-out in the worker entry point, not in the task module:
import { runlane } from './runlane.js'
import { billingQueue } from './tasks/sync-account.js'
const worker = await runlane.createDeliveryWorker({
concurrency: 8,
queues: [billingQueue.name],
})
const close = () => void worker.close()
process.once('SIGTERM', close)
try {
await worker.waitUntilClosed()
} finally {
process.off('SIGTERM', close)
await worker.close()
await runlane.close()
}This worker opens eight local slots, but storage still prevents any durable partition from exceeding its limit. Keep worker fan-out high enough to use available partitions without treating it as the global policy.
To test the limit:
- Block several handlers for one key.
- Start more worker slots than the queue limit.
- Confirm only the allowed number starts.
- Release one and confirm another begins.
- Repeat with two keys to prove the partitions progress independently.
If work remains queued while other partitions are idle, confirm every producer and worker registered the same queue definition. Also check for active leases or dispatch reservations still occupying the partition before raising the limit.