Dead Letter Queue: Handle Failed Jobs & Auto-Retry in bunqueue
guide · dlq
Failed jobs, kept in the DLQ.
The Dead Letter Queue stores failed jobs with full metadata for debugging and recovery. Inspect why a job died, retry it automatically or by hand, and expire old entries on a policy.
7 tracked failure reasons
7 days default retention before purge
10,000 max entries per queue
Why Jobs End Up in DLQ
Section titled “Why Jobs End Up in DLQ”| Reason | Description |
|---|---|
explicit_fail | Job explicitly failed via error throw |
max_attempts_exceeded | Job exceeded retry attempts |
timeout | Job timed out during processing |
stalled | Job stalled (no heartbeat) |
ttl_expired | Job TTL expired before processing |
worker_lost | Worker disconnected during processing |
unknown | Fallback for unclassified failures |
Configuration
Section titled “Configuration”import { Queue } from 'bunqueue/client';
const queue = new Queue('my-queue', { embedded: true });
queue.setDlqConfig({ autoRetry: true, // Enable automatic retry from DLQ autoRetryInterval: 3600000, // Retry every hour maxAutoRetries: 3, // Max 3 auto-retries maxAge: 604800000, // Purge after 7 days (null = never) maxEntries: 10000, // Max entries per queue});Options
Section titled “Options”| Option | Default | Description |
|---|---|---|
autoRetry | false | Enable automatic retry |
autoRetryInterval | 3600000 | Time between auto-retries (1 hour) |
maxAutoRetries | 3 | Maximum auto-retry attempts |
maxAge | 604800000 | Auto-purge age (7 days, null = never) |
maxEntries | 10000 | Maximum DLQ entries per queue |
Viewing DLQ Entries
Section titled “Viewing DLQ Entries”// Get all DLQ entriesconst entries = queue.getDlq();
entries.forEach(entry => { console.log('Job ID:', entry.job.id); console.log('Reason:', entry.reason); console.log('Error:', entry.error); console.log('Entered DLQ:', new Date(entry.enteredAt)); console.log('Attempts:', entry.attempts.length); console.log('Retry count:', entry.retryCount); console.log('Next retry:', entry.nextRetryAt); console.log('Expires:', entry.expiresAt);});Filtering
Section titled “Filtering”// Filter by reasonconst stalledJobs = queue.getDlq({ reason: 'stalled' });const timeoutJobs = queue.getDlq({ reason: 'timeout' });
// Filter by ageconst oldJobs = queue.getDlq({ olderThan: Date.now() - 86400000 // Older than 24 hours});
const recentJobs = queue.getDlq({ newerThan: Date.now() - 3600000 // Last hour});
// Filter retriable entriesconst retriable = queue.getDlq({ retriable: true });
// Paginationconst page = queue.getDlq({ limit: 10, offset: 20 });Statistics
Section titled “Statistics”const stats = queue.getDlqStats();
console.log('Total entries:', stats.total);console.log('By reason:', stats.byReason);// { explicit_fail: 5, timeout: 2, stalled: 1, ... }
console.log('Pending retry:', stats.pendingRetry);console.log('Expired:', stats.expired);console.log('Oldest entry:', new Date(stats.oldestEntry));console.log('Newest entry:', new Date(stats.newestEntry));Retrying Jobs
Section titled “Retrying Jobs”// Retry all jobsconst count = queue.retryDlq();
// Retry specific jobqueue.retryDlq('job-123');
// Retry by filterqueue.retryDlqByFilter({ reason: 'timeout' });queue.retryDlqByFilter({ olderThan: Date.now() - 86400000 });Purging
Section titled “Purging”// Purge all DLQ entriesconst purged = queue.purgeDlq();console.log(`Purged ${purged} entries`);DLQ Entry Structure
Section titled “DLQ Entry Structure”interface DlqEntry<T> { job: Job<T>; // The failed job enteredAt: number; // When first moved to DLQ reason: FailureReason; // Why it failed error: string | null; // Error message attempts: AttemptRecord[]; // Full attempt history retryCount: number; // Times retried from DLQ lastRetryAt: number | null; // Last DLQ retry time nextRetryAt: number | null; // Next scheduled auto-retry expiresAt: number | null; // When entry expires}
interface AttemptRecord { attempt: number; // Attempt number (1-based) startedAt: number; // When attempt started failedAt: number; // When attempt failed reason: FailureReason; // Failure reason error: string | null; // Error message duration: number; // Attempt duration (ms)}Auto-Retry Behavior
Section titled “Auto-Retry Behavior”When autoRetry is enabled:
- Failed jobs are added to DLQ with
nextRetryAtset - Background task checks for due retries every minute
- Jobs are re-queued with reset attempt count
- Uses exponential backoff:
interval * 2^(retryCount-1) - After
maxAutoRetries, job stays in DLQ permanently
queue.setDlqConfig({ autoRetry: true, autoRetryInterval: 60000, // Base: 1 minute maxAutoRetries: 3,});
// Retry schedule (backoff multiplier is 2^(retryCount-1) at scheduling time):// 1st retry: 1 minute after entering the DLQ// 2nd retry: 1 minute after the 1st retry (60s × 2^0)// 3rd retry: 2 minutes after the 2nd retry (60s × 2^1)// After that: no more auto-retriesExample: Monitoring Dashboard
Section titled “Example: Monitoring Dashboard”// Poll DLQ stats every 30 secondssetInterval(() => { const stats = queue.getDlqStats();
// Alert if too many failures if (stats.total > 100) { alertOps('High DLQ count', stats); }
// Check for stall issues if (stats.byReason.stalled > 10) { alertOps('Many stalled jobs - check workers', stats); }}, 30000);