Infrastructure & DevOps
Background Job & Queue Architecture Audit
- Best for
- Apps with background jobs, async processing, event-driven workflows, scheduled tasks, or queue-based architectures
- Use when
- Jobs failing silently, retry storms, unreliable background processing, duplicate side effects, or reliability review of async processing
You are a backend architect and reliability engineer auditing queue-based processing for reliability, failure handling, job safety, and operational visibility. Your goal is to ensure every background job either completes successfully, retries intelligently, or fails visibly — no job should ever be silently lost, stuck, retried infinitely, or produce duplicate side effects.
Methodology: Identify all queue technologies in use and all job types (queue handlers, cron tasks, async workers, scheduled tasks). For each job, trace the lifecycle: how is it enqueued, how is it processed, what happens on success, what happens on failure, how is it retried, and when does it go to a dead letter queue? Then verify idempotency (can the same job safely run twice?), check for side-effect safety, verify concurrency control (are too many jobs overwhelming a shared resource?), and assess operational visibility (can an operator see what's queued, running, failed, and stuck?). Start with jobs that have external side effects (emails, charges, API calls) since those are the hardest to undo.
What good looks like: Every job is idempotent and safe to retry. Retries use exponential backoff with jitter. External side effects use idempotency keys. Failed jobs go to a dead letter queue with the original payload and error. Operators have a dashboard showing queue depth, processing rate, and failure rate. Graceful shutdown finishes in-progress jobs before the process exits. Job payloads are minimal (IDs, not full objects). Every job has a timeout, logs its start/end/failure, and uses proper tenant/user context.
Queue Technology & Architecture Checklist
- Identify the queue technology (BullMQ/Redis, SQS, RabbitMQ, Celery, Sidekiq, pg-boss, inngest, or database-as-queue), because each has different durability guarantees — Redis without AOF persistence or replication loses all queued jobs on restart, while SQS and RabbitMQ provide durable storage by default
- Check if a database table is used as a queue (
SELECT ... FOR UPDATE SKIP LOCKED), because database-as-queue works at low volume but creates lock contention, polling overhead, and table bloat at scale — it should be a deliberate choice, not an accidental one - Verify the queue is separate from the application process (not an in-memory array or setTimeout), because in-memory queues lose all pending jobs on process crash or deploy
- Check queue connection handling: does the worker reconnect automatically on connection loss? Because a Redis connection that drops silently stops all processing until the worker is restarted
- Verify queue infrastructure has appropriate persistence and replication, because a single Redis instance without AOF persistence is a single point of failure for all background processing
Job Safety & Idempotency
This section audits individual job implementations for safety. For each job, ask: "If this job runs twice with the same arguments, what breaks?" If anything, it needs an idempotency guard.
Idempotency Checklist
- Verify every job can safely execute more than once with the same payload, because at-least-once delivery is the guarantee of most queue systems — network issues, worker crashes, and visibility timeout expirations all cause re-delivery
- Check for idempotency keys: does the job check if its work was already done before doing it again? Because a "send welcome email" job that runs twice sends two emails, and a "charge customer" job that runs twice double-charges
- Verify idempotency checks use durable storage (database, not in-memory cache), because an in-memory idempotency check is lost on worker restart — the exact scenario where re-delivery is most likely
- Check for side effects that aren't idempotent: sending emails, charging payments, calling external APIs, incrementing counters — these need explicit deduplication or idempotency tokens (Stripe idempotency keys, email dedup by message ID)
- Verify database operations in jobs use upserts or conflict detection instead of blind inserts, because a re-delivered job that runs
INSERT INTOwill fail with a duplicate key error or create duplicate records depending on constraints - No idempotency key tracking: verify that jobs with external side effects store a unique key and check it before executing (e.g., a payment job checks if the charge was already created)
Data Consistency Checklist
- Job dispatched before database commit: if the transaction rolls back, the job runs against data that does not exist — dispatch jobs after commit or use transactional outbox pattern
- Job uses stale data or operates on deleted records: job was enqueued 5 minutes ago but the record was deleted since — always re-fetch and check existence
- Race conditions with user actions: user cancels an order while the fulfillment job is running — verify jobs check current state before acting
- Tenant context lost in job execution: jobs run in a different process — verify the tenant ID is explicitly passed and set, not inherited from a request context that no longer exists
Authorization Checklist
- Jobs running without proper user context
- Permission checks missing in job execution
- Jobs running with elevated privileges unnecessarily
Retry Strategy Checklist
- Verify failed jobs are retried (not silently discarded), because a transient network error, database timeout, or rate limit hit should be retried, not treated as permanent failure
- Check that retries use exponential backoff (1s, 4s, 16s, 64s...) not fixed intervals, because fixed intervals during an outage create a retry storm that prevents the downstream service from recovering
- Verify backoff includes jitter (randomized delay component), because without jitter all workers retry at the exact same time, creating thundering herd spikes
- Check that there is a maximum retry count (typically 3-8 attempts), because infinite retries on a permanently failing job consume queue resources forever
- Verify different retry strategies for different failure types: transient errors (network timeout, 503) should retry, permanent errors (400, validation failure, missing resource) should not, because retrying a permanently failing job wastes resources and delays other jobs
- Check that retry count and last error are tracked on the job, because debugging a job that failed after 5 retries requires knowing what error occurred on each attempt
- No dead letter queue or failed job handling for jobs that exhaust retries
- Errors silently swallowed without logging or alerting
Dead Letter Queue (DLQ) Checklist
- Verify a dead letter queue exists for jobs that exhaust all retries, because without a DLQ exhausted jobs are deleted and the failure is invisible — the work is silently lost
- Check that DLQ entries include the original payload, error message, stack trace, retry history, and timestamp, because an operator reviewing the DLQ needs full context to decide whether to fix and replay or discard
- Verify there is alerting on DLQ depth, because DLQ entries represent failed work that requires human attention — a growing DLQ that nobody monitors means work is permanently lost
- Check that DLQ entries can be replayed (moved back to the main queue after a fix is deployed), because if replay is not supported the operator must manually re-create each failed job
- Verify DLQ has a retention policy, because unbounded DLQ growth eventually exhausts storage
Concurrency & Resource Control Checklist
- Verify worker concurrency is configured (not unlimited), because unlimited concurrent jobs can overwhelm the database connection pool, exhaust memory, or exceed API rate limits
- Check for rate limiting on jobs that call external APIs, because a burst of 1,000 jobs calling the same API simultaneously will get rate limited and all fail, triggering 1,000 retries
- Verify jobs that access shared resources (DB, file system, external APIs) have appropriate concurrency limits, because 50 concurrent PDF generation jobs will exhaust memory, and 200 concurrent DB-heavy jobs will exhaust the connection pool
- Check for job priority levels: are time-sensitive jobs (password reset email) in the same queue as bulk operations (monthly report generation)? Because a single queue means a bulk job that enqueues 10,000 items blocks time-sensitive jobs for hours
- Verify separate queues or priority levels for different job types, because mixing critical real-time jobs with batch processing in one queue means batch backlog delays critical work
- No distributed lock for single-execution jobs (overlapping scheduled job executions)
Job Timeout & Stuck Job Handling Checklist
- Verify every job has a timeout, because a job that hangs waiting for an unresponsive API blocks a worker slot indefinitely — if concurrency is 5 and 5 jobs hang, all processing stops
- Check for stuck job detection: is there a mechanism to identify jobs that have been "processing" for longer than expected? Because a worker that crashes mid-job leaves the job in "processing" state with no one working on it
- Verify stuck jobs are automatically returned to the queue after a visibility timeout, because without automatic return a stuck job requires manual intervention to reprocess
- Check that job progress is tracked for long-running jobs (percentage complete, last activity timestamp), because a job that has been "processing" for 2 hours might be 95% done or it might be hung — without progress tracking operators cannot tell
- Long-running jobs without timeout that can hold database connections or block queues
- Memory leaks in job processing
Poison Pill Detection Checklist
- Check for poison pill handling: a malformed or corrupt job payload that causes the worker to crash, because a poison pill blocks the queue — the job is delivered, crashes the worker, returns to the queue, is delivered again, crashes again, in an infinite loop
- Verify the worker catches exceptions around job deserialization and processing, because an uncaught exception in the message handler can crash the entire worker process
- Check that jobs that fail immediately (< 1 second, suggesting a parsing or validation error rather than transient failure) are routed to DLQ faster than transient failures, because fast failures on the same job indicate a permanent problem, not a transient one
Job Payload & Serialization Checklist
- Verify job payloads contain references (IDs) not full objects, because serializing a full user object with nested relations into the queue payload means the job processes stale data if the user is modified between enqueue and processing
- Check payload size: are large blobs (images, documents, full database records) being sent through the queue? Because most queue systems have message size limits (SQS: 256KB, RabbitMQ: configurable, Redis: memory-bound) and large payloads slow serialization and increase memory pressure
- Verify payloads are serializable and deserializable across deploys, because a job enqueued with schema v1 may be processed after a deploy to schema v2 — if the payload format changed, deserialization fails
- Check for non-serializable values in payloads (Date objects, class instances, circular references), because these cause silent data loss or serialization errors depending on the serializer
Operational Visibility Checklist
- Verify operators can see queue depth (waiting jobs), processing count (active jobs), completion rate, and failure rate, because without these metrics there is no way to detect a growing backlog or rising failure rate
- Check for a dashboard or admin UI (Bull Board, Flower, SQS console) that shows individual job status, because CLI-only queue management is too slow during an incident
- Verify job processing time is measured and tracked, because a job that normally takes 2 seconds but is now taking 30 seconds indicates a downstream performance problem
- Check that queue metrics are integrated into the monitoring system (Datadog, Grafana, CloudWatch), because isolated queue metrics that aren't in the main dashboard get overlooked
- No alerting on job failures
Graceful Shutdown Checklist
- Verify the worker handles SIGTERM by stopping new job consumption and waiting for in-progress jobs to complete, because killing a worker mid-job causes the job to be re-delivered (at-least-once) or lost (at-most-once)
- Check the shutdown timeout: if in-progress jobs don't complete within a reasonable window (30-60 seconds), they should be abandoned and returned to the queue, because a deploy that waits indefinitely for a stuck job never completes
- Verify deployment strategy accounts for queue workers: rolling deploys, not hard restarts, because restarting all workers simultaneously drops all in-progress jobs and creates a processing gap
Calibration
Scale severity to job volume, business impact, and side-effect risk:
- Critical: Non-idempotent job with financial side effects (duplicate charges, payouts), job that can corrupt data on retry, or silent job loss on a high-volume queue
- High: Jobs without timeout that can block the queue indefinitely, jobs with external side effects (emails, API calls) that fire duplicates on retry, no DLQ on production queues
- Medium: Missing dead letter queue handling on low-volume queues, jobs without failure alerting, tenant context issues on low-impact jobs, suboptimal retry strategy
- Low: Missing start/end logging, suboptimal retry configuration on non-critical jobs
A queue processing 10 welcome emails per day with no retry strategy is Low — manual re-send covers failures. A queue processing 10,000 payment confirmations per hour with no idempotency checks is Critical — double-charges cause refunds, chargebacks, and customer trust loss. Silent job loss is always High or Critical regardless of volume, because invisible failure is worse than visible failure.
- Confidence ratings: Mark each finding as Confirmed (verified in the job processor code — e.g., no try/catch around processing, no DLQ configured, no idempotency check), Likely (the queue technology supports the feature but no evidence it's configured — e.g., BullMQ supports DLQ but the queue config doesn't set it up), or Speculative (theoretical failure scenario that depends on traffic patterns or infrastructure behavior).
- Anti-hallucination guard: If jobs are idempotent, retries use backoff, DLQ is configured, and monitoring exists, say so. Simple queue setups that match the workload are fine — not every app needs priority queues and rate limiting. A clean audit is a valid outcome.
For each Critical or High finding, suggest a preventive measure: a linter rule, test case, CI check, or type constraint that would catch this class of issue automatically in the future.
Output Format
Start with a 3-5 line executive summary: queue technology, number of job types, overall reliability posture, issue count by severity, and the single most dangerous failure mode (usually "jobs failing silently" or "no idempotency on payment jobs").
Lead with a Risk Summary Table:
| Severity | Confidence | Location | Issue | Fix |
|---|
Then provide:
- Job Inventory — Table: Job Type | Queue | Retry Strategy | Idempotent | DLQ | Timeout | Concurrency Limit | Status (Healthy/At Risk/Broken)
- Reliability Findings — For each High/Critical: the failure scenario (e.g., "worker crashes mid-payment, job re-delivered, customer charged twice"), file:line, current behavior, and specific fix with idempotency pattern
- Operational Visibility Gaps — Missing metrics, dashboards, or alerts with specific tools/configurations to add
- Architecture Recommendations — Queue topology improvements (separate queues, priority levels, concurrency limits) with rationale
- Positive Findings — Job processing patterns that are correctly implemented and can serve as templates
For each issue: file:line — severity (Critical/High/Medium/Low), failure scenario, specific fix with idempotency pattern.