Skip to main content

Rate Limiting

Named workstreams can be rate limited by adding a rateLimit configuration to the workstream. max and duration bound how many of that workstream's jobs may start per time window:

workersApp.set('background', {
...

namedWorkstreams: [
{
name: 'Iterable',
workerCount: 1,
concurrency: 10,

rateLimit: {
max: 10,
duration: 1000,
},
},
{
name: 'Twilio',
workerCount: 1,
concurrency: 10,

// 20 jobs per second
rateLimit: {
max: 20,
duration: 1000,
},
},
],
})

...
})

rateLimit becomes the BullMQ limiter on that workstream's workers, which is an open-source BullMQ feature — no BullMQ Pro license is involved.

Reacting to a 429

A rate limit you configure is a guess at the external service's budget. When the service itself tells you to slow down — an HTTP 429 carrying a retry-after — throw RateLimitedPsychicJob to pause the whole workstream for that duration without burning one of the job's retry attempts:

import { RateLimitedPsychicJob } from '@rvoh/psychic-workers/errors'

if (response.status === 429) {
throw new RateLimitedPsychicJob({
pauseQueueForSeconds: Number(response.headers.get('retry-after') ?? 1),
})
}

Because the pause doesn't consume an attempt, a service that keeps answering 429 could cycle one job through the pause indefinitely. maxStartedAttempts bounds that; set it on the global defaultBullMQWorkerOptions. Any workstream whose jobs throw the signal wants it.

Throw RateLimitedPsychicJob only from a job on a workstream whose workers carry a limiter — thrown where there is none, it surfaces as a misconfiguration rather than pausing anything.