Debugging BullMQ Flow Producer Jobs: Parent-Child Chains & Workflow Topologies
Single-job workers are straightforward to debug — check the job, check the worker, fix the error, move on. But the moment you model a business process as a multi-step BullMQ flow — with fan-out children, deeply nested chains, and cross-queue dependencies — debugging goes from "check the logs" to "find the needle in a dependency tree."
The most common production incidents with BullMQ flows aren't about Redis connections or worker crashes. They're about dependency topology failures: stuck parent jobs, orphaned children, waiting-children deadlocks, and invisible chain breaks that silently halt entire workflows.
In this guide, you'll learn how to debug BullMQ Flow Producer workflows systematically — from understanding the dependency model to using introspection APIs to leveraging visual tools like Queue Hub for real-time dependency graphs.
What this guide covers:
- Understanding BullMQ's flow dependency model (parent-child, waiting-children, chain execution)
- Debugging the 5 most common flow failures
- Using
getDependencies(),getChildrenValues(), and state introspection tools - Handling partial failures with
ignoreDependencyOnFailure - Visualizing flow topology with Queue Hub's job detail and dependency views
- Dynamic flow patterns (runtime child creation) and their debugging pitfalls
// The simplest flow that can go wrong
import { FlowProducer } from 'bullmq';
const flowProducer = new FlowProducer({ connection });
// Looks harmless — but what happens when "validate-payment" fails?
const flow = await flowProducer.add({
name: 'complete-order',
queueName: 'orders',
data: { orderId: 'ord-123' },
children: [
{ name: 'validate-payment', queueName: 'payments', data: { orderId: 'ord-123' } },
{ name: 'check-inventory', queueName: 'inventory', data: { orderId: 'ord-123' } },
{ name: 'calculate-shipping', queueName: 'shipping', data: { orderId: 'ord-123' } },
],
});
How BullMQ Flows Actually Work (The Mental Model)
Key insight most developers miss: A parent job does not simply "wait" for children. BullMQ implements a dependency barrier — the parent enters the waiting-children state and is not moved to wait until every child has reached a terminal state (completed, failed, or explicitly ignored).
The Lifecycle of a Flow
FlowProducer.add()
├── All jobs added atomically (single Redis MULTI/EXEC)
├── Children are immediately eligible for workers
├── Parent enters "waiting-children" state
├── Children process (any queue, any worker)
│ ├── All children succeed → parent moves to "wait"
│ ├── Any child fails (without ignoreDependencyOnFailure)
│ │ └── Parent stays in "waiting-children" forever ← 🔴 MOST COMMON BUG
│ └── Any child fails (with ignoreDependencyOnFailure)
│ └── Parent moves to "wait" once remaining children complete
└── Parent processes → uses getChildrenValues() to aggregate results
State Transitions
┌──────────────┐
│ added │
└──────┬───────┘
│
┌──────▼───────┐
│ waiting- │◄──── Workers pick up & process children
│ children │
└──────┬───────┘
│
┌──────▼───────┐ ┌───────────────┐
│ wait │────►│ active │
└──────┬───────┘ └───────┬───────┘
│ │
│ ┌──────▼──────┐
│ │ completed │ (parent can access children's results)
│ └─────────────┘
│
(If children fail without ignoreDependencyOnFailure)
└─────────────────────► STUCK in waiting-children (never reaches wait)
Checking Flow State Programmatically
import { Job } from 'bullmq';
async function diagnoseFlowState(job: Job) {
const state = await job.getState();
console.log(`Job ${job.id} state: ${state}`);
if (state === 'waiting-children') {
const { processed, unprocessed, failed, ignored } =
await job.getDependenciesCount();
console.log('Dependency health:', {
processed,
unprocessed,
failed,
ignored,
});
// Deep dive into which children are problematic
const deps = await job.getDependencies({
failed: { count: 100, cursor: 0 },
unprocessed: { count: 100, cursor: 0 },
});
console.log('Failed children:', deps.failed);
console.log('Unprocessed children:', deps.unprocessed);
}
}
The 5 Most Common Flow Failures (And How to Debug Them)
1. Parent Stuck in waiting-children Forever
Symptom: A parent job has been in waiting-children for hours or days. The workflow never completes. Downstream processes stall.
Root cause: One or more child jobs failed, and the parent is waiting for ALL children to finish — but failed children are still counted as dependencies.
Debugging with BullMQ APIs:
async function findOrphanFlows(queueName: string) {
const { FlowProducer, Queue } = require('bullmq');
const queue = new Queue(queueName, { connection });
// Get all jobs in waiting-children state
const stuckParents = await queue.getJobs(['waiting-children']);
for (const parent of stuckParents) {
const { failed, unprocessed } =
await parent.getDependenciesCount({ failed: true, unprocessed: true });
console.log(`Parent ${parent.id}: ${failed} failed, ${unprocessed} unprocessed`);
if (failed > 0 && unprocessed === 0) {
// All children have been attempted, some failed — this is a deadlock
console.log(`⚠️ Deadlock detected: Parent ${parent.id} has ${failed} failed children`);
const deps = await parent.getDependencies({
failed: { count: 100, cursor: 0 },
});
for (const [jobKey, jobData] of Object.entries(deps.failed || {})) {
console.log(` Failed child: ${jobKey}`, jobData);
}
}
}
}
The fix — ignoreDependencyOnFailure:
// Correct: tell BullMQ this child's failure shouldn't block the parent
const flow = await flowProducer.add({
name: 'complete-order',
queueName: 'orders',
data: { orderId: 'ord-123' },
children: [
{
name: 'validate-payment',
queueName: 'payments',
data: { orderId: 'ord-123' },
opts: { ignoreDependencyOnFailure: true }, // 👈 key addition
},
{
name: 'check-inventory',
queueName: 'inventory',
data: { orderId: 'ord-123' },
opts: { ignoreDependencyOnFailure: true },
},
{
name: 'calculate-shipping',
queueName: 'shipping',
data: { orderId: 'ord-123' },
opts: { ignoreDependencyOnFailure: true },
},
],
});
// Later, in the parent worker, check which children failed:
const childrenValues = await job.getChildrenValues();
const ignoredFailures = await job.getIgnoredChildrenFailures();
2. Deep Chain Breaks (Nested Dependency Collapse)
Symptom: In a deeply nested chain (step-1 → step-2 → step-3 → step-4), the chain stops mid-way. Step 3 never starts, but step 1 and 2 completed successfully.
Root cause in serial chains: In a serial chain (each level has one child), the chain is only as strong as its weakest link. If step 2 fails without ignoreDependencyOnFailure, step 3's parent (which IS the step-2 job) stays in waiting-children forever, blocking the entire chain.
Visual representation:
car (engine) ← blocked waiting for children
└─ car (wheels) ← blocked waiting for children + THIS JOB FAILED
└─ car (chassis) ← completed ✓ (but nobody picks up its result)
Debugging — traversing a chain programmatically:
async function traceChain(job: Job) {
const chain = [];
let current: Job | null = job;
while (current) {
const state = await current.getState();
const { failed, processed, unprocessed } =
await current.getDependenciesCount();
chain.push({
id: current.id,
name: current.name,
queueName: current.queueName,
state,
childrenCounts: { failed, processed, unprocessed },
});
// Walk up to parent
if (current.parentKey) {
const parentQueue = new Queue(
current.queueName,
{ connection }
);
const parentId = current.parentKey.split(':').pop();
current = await parentQueue.getJob(parentId!);
} else {
current = null;
}
}
return chain.reverse(); // root first
}
// Usage:
// const chain = await traceChain(failedJob);
// chain.forEach(j => console.log(`${j.name} [${j.state}] — children: ${JSON.stringify(j.childrenCounts)}`));
3. Missing Child Results (Empty getChildrenValues)
Symptom: The parent job runs successfully but getChildrenValues() returns {} — no child results are available.
Common causes:
- Children were added to a different queue than expected, and results are stored under a different key namespace
- Children are still running (parent was picked up prematurely due to a race condition)
- The child job completed without a return value (returned
undefined)
Debugging:
async function debugMissingChildrenValues(job: Job) {
const childrenValues = await job.getChildrenValues();
console.log('Children values:', childrenValues);
// {} — empty
// Step 1: Check dependency counts
const counts = await job.getDependenciesCount();
console.log('Dependency counts:', counts);
// If processed === 0, children haven't finished yet
// Step 2: Directly inspect children
const deps = await job.getDependencies({
processed: { count: 100, cursor: 0 },
unprocessed: { count: 100, cursor: 0 },
});
console.log('Processed children:', deps.processed);
console.log('Unprocessed children:', deps.unprocessed);
// Step 3: Check if children are on the expected queue
if (deps.processed) {
for (const [childKey] of Object.entries(deps.processed)) {
// childKey format: "bull:childQueueName:jobId"
const [, , childQueue, childId] = childKey.split(':');
const childQueueInstance = new Queue(childQueue, { connection });
const childJob = await childQueueInstance.getJob(childId);
if (childJob) {
console.log(`Child ${childId} return value:`, childJob.returnvalue);
}
}
}
}
4. Dynamic Child Addition Deadlock
Symptom: A parent job dynamically creates child jobs at runtime (using moveToWaitingChildren and WaitingChildrenError), but then never gets re-evaluated.
The broken pattern:
// ❌ BROKEN PATTERN — parent never re-executes
const worker = new Worker('queue', async (job) => {
if (job.name === 'parent') {
// Check if children already exist
const { processed, unprocessed } = await job.getDependenciesCount();
if (!processed && !unprocessed) {
// Add children dynamically
await queue.add('child', { file: 'report-1.pdf' },
{ parent: { id: job.id, queue: job.queueQualifiedName } });
await queue.add('child', { file: 'report-2.pdf' },
{ parent: { id: job.id, queue: job.queueQualifiedName } });
}
// Tell BullMQ to wait for children
const shouldWait = await job.moveToWaitingChildren(token);
if (shouldWait) {
throw new WaitingChildrenError(); // 👈 parent yields
}
}
}, { connection });
// ✅ CORRECT — children must use ignoreDependencyOnFailure
// or the parent will deadlock if even one child fails
await queue.add('child', { file: 'report-1.pdf' }, {
parent: { id: job.id, queue: job.queueQualifiedName },
opts: { ignoreDependencyOnFailure: true }, // 👈 critical
});
Debugging checklist for dynamic flows:
- Are children being added before
moveToWaitingChildrenis called? (Race condition if called too early) - Do children have the correct
parent.idandparent.queuevalues? - Is
ignoreDependencyOnFailureset if child failures are acceptable? - Is
WaitingChildrenErrorimported correctly? (It's a class, not a string) - Check
getDependenciesCount()for unexpectedly zero values
5. Cross-Queue Dependency Mismatches
Symptom: Flow is added but children never get picked up. Parent stays in waiting-children indefinitely despite children appearing to exist.
Root cause: The FlowProducer.add() tree specifies queueName for each child, but the workers are watching different queue names (typo, different prefix, namespace mismatch).
// ❌ MISMATCH
const flow = await flowProducer.add({
name: 'root',
queueName: 'main-queue', // parent queue
children: [
{ name: 'child-1', queueName: 'child-worker-queue', data: {} }, // child queue
],
});
// Worker watching different queue:
const worker = new Worker('children-queue', /* … */); // ❌ TYPO: "children" vs "child"
Queue Hub's multi-backend support shows all queues and their active job counts at a glance. If a child queue has 0 active/0 waiting jobs but the parent is stuck, it's a strong signal the worker is on the wrong queue or not running.
Debugging with BullMQ's Built-in Introspection APIs
getDependencies() — The Swiss Army Knife
// Full paginated inspection of all dependency states
const deps = await job.getDependencies({
processed: { count: 100, cursor: 0 },
unprocessed: { count: 100, cursor: 0 },
failed: { count: 100, cursor: 0 },
ignored: { count: 100, cursor: 0 },
});
// Each returns { jobs: { [jobKey]: jobData }, cursor: string | 0 }
getDependenciesCount() — Quick Health Check
// Quick pulse check
const counts = await job.getDependenciesCount({
failed: true,
unprocessed: true,
});
if (counts.failed > 0) console.log(`${counts.failed} children failed`);
if (counts.failed === 0 && counts.unprocessed > 0) console.log('Children still running');
if (counts.failed === 0 && counts.unprocessed === 0) console.log('All children done');
getChildrenValues() — Collecting Results
// Returns { [jobKey: string]: any }
// jobKey format: "bull:queueName:jobId"
const values = await job.getChildrenValues();
for (const [key, value] of Object.entries(values)) {
const [, queueName, jobId] = key.split(':');
console.log(`Child ${jobId} on ${queueName} returned:`, value);
}
getIgnoredChildrenFailures() — Post-Mortem on Ignored Failures
// Only works if children were added with ignoreDependencyOnFailure: true
const failures = await job.getIgnoredChildrenFailures();
// Returns array of { jobId, queueName, failedReason, timestamp }
for (const f of failures) {
console.warn(`Ignored child ${f.jobId} on ${f.queueName} failed: ${f.failedReason}`);
}
parentKey — Walking the Chain Upward
// Each job has a parentKey if it's a child in a flow
// Format: "bull:parentQueueName:parentJobId"
if (job.parentKey) {
const parts = job.parentKey.split(':');
const parentQueueName = parts[1];
const parentJobId = parts[2];
console.log(`Job ${job.id} is a child of ${parentJobId} on queue ${parentQueueName}`);
}
Using Queue Hub to Debug BullMQ Flow Workflows
Queue Hub provides visual tooling that replaces dozens of getDependencies() console.log calls with a single click.
Job Detail View — Full Flow Context
Queue Hub's job detail view shows every piece of flow metadata at a glance:
- Job State —
waiting-children,waiting,active,completed,failedwith timestamps - Parent Key — direct link to the parent job (click to navigate)
- Dependency Counts — processed, unprocessed, failed, ignored — displayed as a visual badge
- Children Queue — which queue each child belongs to (critical for cross-queue flows)
- Return Values — what each child returned (no more manual
getChildrenValues()debugging)
┌────────────────────────────────────────────┐
│ Job: complete-order (#job-abc-123) │
│ Queue: orders │
│ State: waiting-children ⏳ (since 14:23) │
│ │
│ ┌─ Dependencies ───────────────────────┐ │
│ │ ✅ 2 processed │ │
│ │ ❌ 1 failed ← click to inspect │ │
│ │ ⏳ 0 unprocessed │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌─ Failed Children ───────────────────┐ │
│ │ validate-payment (payments) │ │
│ │ Error: Insufficient funds │ │
│ │ [View Full Error] [Retry Child] │ │
│ └──────────────────────────────────────┘ │
└────────────────────────────────────────────┘
Dependency Graph Visualization
Queue Hub renders the flow tree as an interactive directed graph, where you can:
- Zoom and navigate deep chains (20+ levels)
- Color-code by state — green = completed, red = failed, yellow = active, gray = waiting-children
- Click any node to jump to that job's detail view
- See dependency arrows showing the parent → child direction
- Detect orphans — children whose parent is missing or has been removed
┌─────────────┐
│ complete- │
│ order │ ← waiting-children (1 failed)
└──────┬──────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ validate │ │ check- │ │ calculate│
│ payment │ │ inventory│ │ shipping │
└─────┬────┘ └──────────┘ └──────────┘
│
┌─────▼──────┐
│ Insufficient│
│ funds │ ← 🔴 FAILED (blocking parent)
└────────────┘
Live Queue Monitoring for Flow Workers
Queue Hub's live worker view shows which workers are actively processing flow children. For multi-queue flows, this surfaces:
- Which queues have no active workers (children are never picked up)
- Which workers are overloaded (children queuing up)
- Job processing times per queue, helping identify bottlenecks
Advanced Flow Debugging Patterns
Writing a Flow Diagnostic Script
Keep this script ready in your codebase:
import { Queue, Job, FlowProducer } from 'bullmq';
import { Redis } from 'ioredis';
const connection = new Redis({ host: 'localhost', port: 6379, maxRetriesPerRequest: null });
interface FlowDiagnosticResult {
healthy: boolean;
parentState: string;
childrenCounts: { processed: number; failed: number; unprocessed: number; ignored: number };
childrenDetails: Array<{
id: string;
queueName: string;
state: string;
failedReason?: string;
returnValue?: any;
}>;
chainLength?: number;
recommendedAction?: string;
}
async function diagnoseFlow(parentJobId: string, queueName: string): Promise<FlowDiagnosticResult> {
const queue = new Queue(queueName, { connection });
const parent = await queue.getJob(parentJobId);
if (!parent) {
return { healthy: false, parentState: 'unknown', childrenCounts: { processed: 0, failed: 0, unprocessed: 0, ignored: 0 }, childrenDetails: [], recommendedAction: 'Parent job not found' };
}
const state = await parent.getState();
const counts = await parent.getDependenciesCount();
const deps = await parent.getDependencies({
processed: { count: 100, cursor: 0 },
failed: { count: 100, cursor: 0 },
unprocessed: { count: 100, cursor: 0 },
ignored: { count: 100, cursor: 0 },
});
const childrenDetails = [];
for (const [key, data] of Object.entries(deps.failed || {})) {
const [, , childQueue, childId] = key.split(':');
childrenDetails.push({ id: childId, queueName: childQueue, state: 'failed', failedReason: data?.failedReason });
}
for (const [key] of Object.entries(deps.unprocessed || {})) {
const [, , childQueue, childId] = key.split(':');
childrenDetails.push({ id: childId, queueName: childQueue, state: 'unprocessed' });
}
for (const [key] of Object.entries(deps.processed || {})) {
const [, , childQueue, childId] = key.split(':');
childrenDetails.push({ id: childId, queueName: childQueue, state: 'processed' });
}
// Determine health
const healthy = state !== 'waiting-children' || (counts.failed === 0 && counts.unprocessed === 0);
// Recommended action
let recommendedAction: string | undefined;
if (state === 'waiting-children' && counts.failed > 0 && counts.unprocessed === 0) {
recommendedAction = 'All children have finished but some failed. Either set ignoreDependencyOnFailure: true on child jobs, or retry the failed children.';
} else if (state === 'waiting-children' && counts.unprocessed > 0) {
recommendedAction = 'Children are still pending. Check if workers are running on the child queues.';
}
return { healthy, parentState: state, childrenCounts: counts, childrenDetails, recommendedAction };
}
// Usage:
// const result = await diagnoseFlow('job-abc-123', 'orders');
// console.log(result.healthy ? '✅ Healthy' : `❌ Issue: ${result.recommendedAction}`);
Automatic Deadlock Detection with Queue Hub Webhooks
For teams running flows in production, configure alerts when:
- A job has been in
waiting-childrenfor more than X minutes - A
failedchild count exceeds a threshold - A
completedparent has nochildrenValues(unexpected empty chain) - Dependency count doesn't match expected children (some were lost)
Queue Hub's monitoring integrates with these signals, showing flow health at a glance across all your queues.
Flow Debugging Decision Tree
Is a parent job stuck?
│
├─ Is state "waiting-children"?
│ ├─ YES → Check dependency counts:
│ │ ├─ failed > 0, unprocessed = 0
│ │ │ └─ 🔴 All children finished but some failed
│ │ │ → Add ignoreDependencyOnFailure: true
│ │ │ → Or retry failed children
│ │ │
│ │ ├─ unprocessed > 0
│ │ │ └─ 🔴 Children still pending
│ │ │ → Are workers running on child queues?
│ │ │ → Are child queue names correct?
│ │ │ → Use Queue Hub to check worker status
│ │ │
│ │ └─ processed === 0, failed === 0
│ │ └─ 🔴 No children exist at all
│ │ → FlowProducer.add() may have failed silently
│ │ → Check if children were added atomically
│ │
│ └─ NO → Is parent in "completed" with no children values?
│ └─ 🟡 Check if children returned values
│ → Verify worker returns data from processor
│
├─ Is parent stuck in "waiting-children" with dynamic children pattern?
│ └─ Are children added with opts.parent correctly set?
│ → Verify parent.id and parent.queue
│ → Check moveToWaitingChildren() called after adding children
│ → Ensure WaitingChildrenError is imported
│
└─ Is a deep chain broken?
└─ Walk the chain upward using parentKey
→ TraceChain() utility (see above)
→ Find the first job in "waiting-children" or "failed"
Best Practices for Flow Producer Workflows
-
Always set
ignoreDependencyOnFailure: trueon non-critical children — unless the parent genuinely must not proceed without every child, make individual child failures non-blocking. -
Add a flow TTL — use
opts.ttlon parent jobs so stuck flows eventually expire:await flowProducer.add({ name: 'expiring-flow', queueName: 'flows', opts: { ttl: 3600000 }, // Auto-clean after 1 hour children: [/* … */], }); -
Log at flow boundaries — in every parent worker, log:
const counts = await job.getDependenciesCount(); console.log(`Flow ${job.id}: processing with ${counts.processed} processed, ${counts.failed} failed`); -
Use
getIgnoredChildrenFailures()in parent workers — don't silently swallow child failures:const failures = await job.getIgnoredChildrenFailures(); if (failures.length > 0) { console.warn(`Flow ${job.id}: ${failures.length} children failed but were ignored`); // Send to your error tracking system } -
Keep flows shallow — deep nesting (5+ levels) is hard to debug. If your chain is deeper, consider splitting into separate orchestrator flows.
-
Use Queue Hub's dependency graph — don't wait for a crisis. Regularly inspect the flow topology of your production queues to catch structural issues early.
FAQ — Flow Debugging Edition
Q: My parent job is stuck in waiting-children but all children completed successfully. What gives?
A: Check if children were completed with a failed return value even though the processor ran. BullMQ distinguishes between "processed" and "failed" — a child that throws but has retries left may be in retry, not completed. Use getDependencies() to inspect the exact status.
Q: Can a child job belong to a different queue than its parent?
A: Yes! FlowProducer supports cross-queue flows. The child's queueName determines its queue. The parent waits regardless of where children live. Use Queue Hub's multi-backend support to see all queues involved in a flow at once.
Q: How do I retry an entire flow from scratch vs from the failed child?
A: BullMQ doesn't have a "retry flow" primitive. To retry from the failed child, retry that specific child job. To retry the entire flow, remove the flow and re-add it via FlowProducer.add(). Queue Hub's job detail view lets you retry individual children with one click.
Q: What happens if I remove a child job manually?
A: Removing a child job from Redis does not update the parent's dependency list. The parent will wait indefinitely for a child that no longer exists. Always remove flows through BullMQ APIs (job.remove()), not by deleting Redis keys directly. Queue Hub's UI uses the correct APIs to prevent this.
Q: Can a child have multiple parents? A: No. BullMQ's flow model is a tree (each child has exactly one parent), not a DAG (a child cannot have multiple parents). For fan-in/multi-parent patterns, use an intermediary orchestrator job.
Conclusion — From "Where's My Flow?" to "I See It All"
BullMQ's Flow Producer is one of the most powerful features for modeling complex business processes as job workflows. But with that power comes a new category of debugging challenges — invisible dependency deadlocks, silent chain breaks, and stuck parent jobs that halt your entire pipeline.
By understanding the waiting-children lifecycle, using ignoreDependencyOnFailure intentionally, and leveraging introspection APIs like getDependencies() and getDependenciesCount(), you can debug any flow failure in minutes rather than hours.
And when you're tired of typing await job.getDependencies() over and over — Queue Hub turns every flow into a visual, clickable graph.
Try Queue Hub
Stop guessing which child failed. Stop console.logging dependency counts. Get a live, interactive dependency graph of every BullMQ flow in your system.
Queue Hub gives you:
- 🔍 Job Detail View — full flow context: parent key, dependency counts, child failures, and return values
- 🌳 Dependency Graph — interactive visualization of parent-child trees, color-coded by state
- 📊 Live Worker View — see which workers are processing flow children in real time
- 🔗 Multi-Backend — connect multiple Redis instances, see cross-queue flows in one dashboard
- 🚀 Agent Tunneling — secure access to private Redis behind VPCs without exposing ports
- 👥 Multi-Org — share flow debugging with your team
Start debugging flows in minutes, not hours. → queuehub.dev — Free tier available. No credit card required.
Appendix: Quick-Reference Flow Debugging API
| API | Returns | Use Case |
|---|---|---|
job.getState() |
'waiting-children' | 'waiting' | 'active' | 'completed' | 'failed' |
Check where a job is in its lifecycle |
job.getDependencies() |
{ processed, unprocessed, failed, ignored } |
Inspect which specific children are in each state |
job.getDependenciesCount() |
{ processed: number, failed: number, unprocessed: number, ignored: number } |
Quick health check of a flow |
job.getChildrenValues() |
{ [jobKey]: returnValue } |
Collect results from all completed children |
job.getIgnoredChildrenFailures() |
Array<{ jobId, queueName, failedReason }> |
Post-mortem on children that failed with ignoreDependencyOnFailure |
job.parentKey |
string | undefined |
Get the parent's fully qualified key to walk the chain upward |
job.moveToWaitingChildren() |
Promise<boolean> |
Tell BullMQ the parent has added children and should wait (dynamic flows) |
WaitingChildrenError |
Error class | Throw in a processor to yield the parent job back to waiting-children |
Written for the Queue Hub engineering blog. First published July 2026.
Related Articles
Beyond BullMQ's Built-In Rate Limiter: Custom Redis-Powered Throttling with Lua Scripts
Go beyond BullMQ's fixed-window rate limiter with custom Redis Lua token buckets, sliding window logs, dynamic adaptive throttling, and multi-layered rate limiting architectures for production queues.
BullMQ Rate Limiting: A Complete Guide
A comprehensive guide to BullMQ rate limiting covering global rate limiters, dynamic throttling, group rate limiting (BullMQ Pro), smoothing strategies, concurrency tuning, and monitoring with Queue Hub.