How to Monitor BullMQ Queues Without Writing Code
You've got BullMQ running in production. Jobs are flowing through. Then one day, something breaks. A job fails silently. Or worse, jobs start piling up and you have no idea why.
The usual response? Add more console.log statements. Write a quick script to query Redis directly. Stare at raw JSON in the terminal at 2 AM.
There's a better way.
The Problem with BullMQ Visibility
BullMQ is fantastic for job processing. It handles retries, delays, priorities, and concurrency out of the box. But when it comes to observability, you're mostly on your own.
The built-in dashboard (Bull Board) helps, but it requires:
- Adding another dependency to your project
- Setting up an Express route
- Deploying and securing another endpoint
- Hoping it doesn't crash your app when you have millions of jobs
And even then, you can't easily:
- See the actual Redis data structure
- Query jobs by custom patterns
- Bulk delete stuck jobs
- Compare queue states across environments
What BullMQ Actually Stores in Redis
Before we fix the problem, let's understand what we're looking at. BullMQ stores everything in Redis using a consistent key pattern:
bull:<queue-name>:id # Job counter
bull:<queue-name>:waiting # List of waiting job IDs
bull:<queue-name>:active # List of active job IDs
bull:<queue-name>:completed # Set of completed job IDs
bull:<queue-name>:failed # Set of failed job IDs
bull:<queue-name>:delayed # Sorted set of delayed jobs
bull:<queue-name>:<job-id> # Hash containing job data
Each job is stored as a Redis Hash with fields like data, opts, progress, returnvalue, failedReason, and timestamps.
The problem? This data is scattered across multiple keys and data types. Piecing it together manually is tedious.
Using Redimo's Pattern Monitor
Here's where pattern-based monitoring changes everything. Instead of writing code to aggregate queue data, you define a pattern and let the tool do the work.
Step 1: Create a Monitor Pattern
In Redimo, create a new Pattern Monitor with:
Pattern: bull:email-queue:*
This instantly captures all keys related to your email queue - waiting lists, active jobs, completed sets, and individual job hashes.
Step 2: View Queue Health at a Glance
The monitor dashboard shows you:
| Metric | What It Tells You |
|---|---|
| Total Keys | Overall queue size |
| Type Distribution | How many waiting vs active vs completed |
| Memory Usage | Is this queue bloating your Redis? |
| TTL Status | Are old jobs being cleaned up? |
Step 3: Drill Into Failed Jobs
Filter by pattern bull:email-queue:failed or look for job hashes with failedReason fields. Redimo renders the job data as an expandable tree, so you can see exactly what failed and why.
No more:
const job = await queue.getJob(jobId);
console.log(JSON.stringify(job.data, null, 2));
console.log(job.failedReason);
Just click and see.
Common BullMQ Debugging Scenarios
Scenario 1: Jobs Stuck in Active State
Workers crashed mid-process, leaving jobs in active that will never complete.
CLI approach:
redis-cli LRANGE bull:myqueue:active 0 -1
# Get each job ID, then...
redis-cli HGETALL bull:myqueue:<job-id>
# Repeat for each stuck job
Redimo approach:
- Pattern Monitor on
bull:myqueue:active - See all stuck job IDs immediately
- Click any job to view full data
- Bulk select and delete, or move back to waiting
Scenario 2: Memory Growing Unexpectedly
Completed jobs aren't being cleaned up.
Check your cleanup settings:
const queue = new Queue('myqueue', {
defaultJobOptions: {
removeOnComplete: 1000, // Keep last 1000
removeOnFail: 5000,
}
});
With Redimo:
- Monitor
bull:myqueue:completed - Check the count - is it in the millions?
- View memory usage per key type
- Bulk delete old completed jobs if needed
Scenario 3: Delayed Jobs Not Processing
Jobs scheduled for the future aren't running when expected.
Redimo approach:
- Monitor
bull:myqueue:delayed - This is a Sorted Set - scores are timestamps
- See exactly when each job is scheduled
- Spot jobs with timestamps in the past (something's wrong)
Bulk Operations Without Code
One of the biggest pain points with BullMQ debugging is bulk operations. Need to delete 10,000 failed jobs? In code:
const failed = await queue.getFailed(0, 10000);
await Promise.all(failed.map(job => job.remove()));
Hope your Node process doesn't run out of memory.
In Redimo:
- Filter to
bull:myqueue:*with typefailed - Select all
- Delete with undo support
The deletion happens server-side. Your laptop doesn't need to hold 10,000 job objects in memory.
Multi-Queue Overview
Most real apps have multiple queues:
bull:email-queue:*bull:payment-queue:*bull:notification-queue:*bull:analytics-queue:*
Create a monitor for each, or use a broader pattern like bull:*-queue:* to see everything. The tree view groups by queue name automatically.
Production Safety
When you're poking around production Redis, accidents happen. Redimo's Safe Mode prevents destructive operations:
- Delete button requires confirmation
- Production connections show warning indicators
- All deletes are undoable (within a time window)
This matters when one wrong DEL command could wipe your entire job history.
Beyond BullMQ
The same pattern-based approach works for:
- Sidekiq (Ruby):
sidekiq:queue:* - Celery (Python):
celery-task-meta-* - Agenda (Node):
agendaJobs.* - Any custom queue: Whatever key pattern you're using
If it's in Redis, you can monitor it.
Getting Started
- Download Redimo (free tier available)
- Connect to your Redis instance
- Create a Pattern Monitor for
bull:* - Start seeing your queues clearly
No code changes. No new dependencies. No additional endpoints to secure.
Your BullMQ setup is already storing great data. You just need a better way to see it.