Priority & Rate Limiting Cheatsheet
One-screen lookup. Cite: Prioritized, Rate limiting, Global Rate Limit, rate-limiter-options.ts.
Priority — order
await queue.add('x', data, { priority: 10 });
await queue.add('x', data, { priority: 5 }); // runs FIRST (lower = higher)
await job.changePriority({ priority: 1 });
const counts = await queue.getCountsPerPriority(0, 1, 5, 10);
| Rule | Detail |
| Lower = higher | Range 1–2 097 152. |
| No priority | Treated as highest — runs before any prioritized job. |
| Ties | FIFO within the same value. |
| Cost | Insert is O(log n) — don't tag every job. |
| No preemption | Only reorders waiting; active jobs finish. |
Rate limiting — throughput
1. Worker limiter (global across workers)
new Worker('q', fn, {
limiter: { max: 10, duration: 1000 }, // 10 jobs/1s, SHARED by all workers
});
Global, not per-worker. 10 workers × {max:10} = still 10/s total. Throttled jobs stay waiting.
2. Queue global limit (queue-side cap)
await queue.setGlobalRateLimit(100, 60_000);
const { max, duration } = await queue.getGlobalRateLimit();
const ttl = await queue.getRateLimitTtl(); // >0 = throttled now
await queue.removeGlobalRateLimit();
Coexists with worker limiter — stricter wins. Neither overrides.
3. Manual / dynamic (HTTP 429)
const worker = new Worker('q', async (job) => {
const [ok, retryAfter] = await api(job.data);
if (!ok) {
await worker.rateLimit(retryAfter ?? 5000);
throw Worker.RateLimitError(); // MUST be this, not a normal Error
}
}, { limiter: { max: 5, duration: 1000 } });
await queue.removeRateLimitKey(); // clear manual hold
| Call | Effect |
worker.rateLimit(ms) | Holds the queue for ms. |
throw Worker.RateLimitError() | Returns job to waiting without consuming a retry. |
queue.getRateLimitTtl() | ms left on hold; 0 = free. |
queue.removeRateLimitKey() | Force-clear manual hold. |
The groupKey trap
limiter: { groupKey: 'tenant' } was removed in BullMQ 3.0. RateLimiterOptions = { max, duration } only. Stale docs/tutorials still show it. Per-tenant throttling now → one queue per tenant, or manual worker.rateLimit() keyed on tenant.
Mental model
| Question | Answer |
| Who runs first? | priority (lower = first) |
| How fast overall? | worker limiter {max,duration} |
| Ceiling across all producers? | setGlobalRateLimit |
| React to a live 429? | rateLimit + RateLimitError |