Observability & Cleanup Cheatsheet

One-screen lookup. Cite: Metrics, Stalled, Removing Jobs, Getters.

Metrics (throughput)

// ENABLE on worker:
new Worker('q', fn, { metrics: { maxDataPoints: MetricsTime.ONE_WEEK * 2 } });

// READ from queue:
const c = await queue.getMetrics('completed', 0, n);  // { data: { values, processedCount } }
const f = await queue.getMetrics('failed');
Per-minute buckets. Sampling, not an audit log. States available: completed, failed.

Counts & jobs (snapshot)

const counts = await queue.getJobCounts();
// { waiting, active, completed, failed, delayed, prioritized,
//   'waiting-children', paused, repeat }

const jobs = await queue.getJobs(['failed'], 0, 50);   // paginated
const job  = await queue.getJob(id);

Stalled jobs

ConceptTruth
"stalled" state?No. Only a 'stalled' event. Job is active with lapsed lock → moved to waiting (or failed).
CauseWorker didn't renew the lock: crash, OOM-kill, or event loop blocked.
FixNot a knob — sandboxed processors for CPU work.
Worker optionDefaultNotes
stalledInterval30000msScan frequency for lapsed locks.
maxStalledCount1After N stalls → permanent failed.
worker.on('stalled', (jobId) => alert(...));

Cleanup — Layer 1: automatic retention (ALWAYS set)

new Queue('q', {
  defaultJobOptions: {
    removeOnComplete: { age: 86_400, count: 2000 },  // ≤N, none older than age
    removeOnFail:    { age: 604_800, count: 5000 },
  },
});
ValueEffect
N (number)Keep last N.
{ age, count }Both bounds (recommended).
trueDelete immediately.
Omitting these = Redis grows until OOM. Most common BullMQ ops failure.

Cleanup — Layer 2: manual methods

MethodRemovesLocked jobs?
job.remove()One jobThrows
queue.clean(graceMs, count, state)≤count in state older than graceThrows
queue.drain()waiting + delayed (NOT completed/failed/active)Skips active
queue.obliterate()Everything — no undoOnly with {force:true}
await queue.clean(3_600_000, 1000, 'failed');  // failed older than 1h, ≤1000
await queue.obliterate({ force: true });          // CI/test only

Health-endpoint recipe

const c = await queue.getJobCounts();
const stalledLag = c.waiting > 5_000;             // backlog alarm
const failRate   = await failRateFromMetrics();   // from getMetrics('failed')