RabbitMQ how to split jobs to tasks and handle results
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
Large jobs are easier to scale when split into independent tasks and processed by multiple workers through RabbitMQ. The challenging part is not publishing tasks, but handling retries, partial failure, and deterministic final aggregation. A robust design uses clear task boundaries, correlation metadata, idempotent workers, and explicit completion rules.
Define Job and Task Boundaries First
Before writing queue code, define the unit of work precisely. A task should run independently and be safe to retry without producing duplicate side effects. Every task message should contain job_id, task_id, and enough payload to execute without shared mutable state.
Good boundaries keep workers stateless and horizontally scalable. Poor boundaries force workers to coordinate through shared memory or synchronous calls, which removes most of the benefit of queue-based processing.
Publish Tasks with Traceable Metadata
Use one exchange for task routing and persist expected task count in your job store. The aggregator needs this value to know when to close a job.
Durable queues and persistent messages improve recovery during broker restarts.
Build Idempotent Workers and Safe Acknowledgement Flow
Workers should ack only after task processing and result publishing both succeed. If either step fails, the message should be retried or dead-lettered according to policy.
If run_unit_of_work writes to external systems, idempotency keys are essential because redelivery is always possible.
Aggregate Results with Explicit Completion Criteria
A dedicated aggregator consumes result messages and updates job state in a database. Track expected_tasks, completed_tasks, and failed_tasks. Mark the job complete only when completed_tasks == expected_tasks.
Do not use queue emptiness as completion signal. Queues may be temporarily empty while workers are still processing or retrying tasks.
A practical aggregator can also store per-task timestamps and error details. This makes root-cause analysis far easier when one task class slows down or fails repeatedly.
Add Bounded Retries and Dead Letter Queues
Unlimited retries create noisy failure loops and hidden load. Configure dead letter routing and enforce small retry ceilings.
When retrying, increment a retry header and stop after a fixed maximum. Alert on dead letter queue growth by routing key.
Prefer Async Completion APIs Over Request-Reply Blocking
RabbitMQ supports request-reply, but batch workflows usually perform better with asynchronous completion. Return job_id to clients and expose a status endpoint instead of holding long HTTP requests.
A typical pattern:
- client submits job and receives
202 Acceptedplusjob_id - client polls
GET /jobs/<id> - aggregator writes terminal state and result location
This model decouples web timeout constraints from queue processing duration.
Common Pitfalls
- Creating tasks that still depend on shared global state.
- Publishing messages without
job_idandtask_idmetadata. - Acknowledging task messages before result persistence.
- Treating queue empty state as job completion.
- Running unlimited retries without dead letter controls.
- Skipping idempotency checks and producing duplicate side effects.
Summary
- Define strict task boundaries and include full execution context per message.
- Publish persistent task messages with correlation metadata.
- Keep worker execution idempotent and acknowledge only after successful completion.
- Aggregate using expected versus completed counters, not queue emptiness.
- Use bounded retries and dead letter queues for controlled failure handling.
- Prefer asynchronous job status APIs for scalable client interaction.
Related reading
- RabbitMQ how to throttle the consumer
- RabbitMQ in Docker - user creation not persisted
- RabbitMQ In pub/sub is the consumer polling the queue for new messages or does the server push messages?
- RabbitMQ install issue on Centos 5.5
- RabbitMQ installation Error
- RabbitMQ IOError Socket
- RabbitMQ Java client - How to sensibly handle exceptions and shutdowns?
- RabbitMQ Java Client Using DefaultConsumer vs QueueingConsumer

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.