Rate Limiting
Token-bucket limits per task or per queue.
Token-bucket limits per task or per queue.
Cap how often a task runs with a token-bucket rate limit. When the bucket is empty, the scheduler reschedules the job slightly into the future rather than failing it.
queue.task("call_api", callApi, {
rateLimit: "100/m", // 100 per minute
});Apply a limit to a whole queue instead:
queue.configureQueue("emails", { rateLimit: "50/s" });rateLimit is a string "<count>/<unit>" where unit is s (second), m
(minute), or h (hour) — e.g. "100/m", "50/s", "3600/h".
Queue-level limits are evaluated before per-task limits. Rate-limited jobs stay
pending and retry on the next tick — they do not consume the retry budget.
Deferral is the right default: the job keeps its place and runs once tokens are
available. But some work is worth less than the backlog it would build — a
metrics sample, a cache warm — and for that, onExcess: "drop" sheds the job
instead of rescheduling it:
queue.task("record_sample", recordSample, {
rateLimit: "10/s",
onExcess: "drop",
});A dropped job is dead-lettered on the spot, not silently deleted. Its
reason is prefixed rate_limit: and its dead-letter metadata is
{"shed":"rate_limit"}, so shedding stays visible in the dashboard and
countable in metrics — an operator can always tell shedding apart from data
loss. The dead-letter auto-retry sweep skips these entries: resurrecting a job
the scheduler deliberately shed would undo the shed.
Three things worth knowing:
onExcess applies to the limit on this task and to the limit on the
queue it runs in — either one rejecting means the same thing to the caller.Flow control walks through what a shed job looks like in the dashboard, and where throttling ends and debouncing begins.
Coming from BullMQ? BullMQ configures rate limiting on the
Worker(limiter: { max, duration }), but it's enforced storage-side across every worker consuming that queue — not literally per process despite living on theWorkerconstructor. flexiq'srateLimitjust moves that config to where the limit conceptually applies: the task (queue.task(name, fn, { rateLimit })) or the queue (configureQueue(name, { rateLimit })), enforced by the scheduler the same way regardless of worker count.
Rate limiting throttles throughput; to bound simultaneous executions use concurrency, and to collapse a burst of enqueues into one run see debouncing.