Back to Blogs
redisrabbitmqbullmqqueuesbackend

Redis, RabbitMQ, and BullMQ in Real Products

Apr 12, 20269 min readBackend

A decision guide based on ownership and failure recovery, not a feature-by-feature comparison.

Redis, RabbitMQ, and BullMQ in Real Products cover
--

The decision table I use

For cached flight search results, losing an entry means another provider call. For a delayed invoice job, losing the job means a customer-facing failure. For an event consumed by several services, each consumer needs an independent delivery path. Those are three different ownership problems, even if all three tools can move or store bytes.

Redis fits latency-sensitive cache and coordination data when the durability and eviction policy match the consequence of loss. BullMQ adds an application job model on Redis: retries, delay, scheduling, concurrency, and job inspection. RabbitMQ is the stronger fit when services exchange messages through explicit routing and acknowledgement semantics.

Redis: decide what may disappear

Redis can be configured for persistence, replication, and strong operational guarantees, so calling it only a cache is inaccurate. The real question is what this deployment guarantees. RDB snapshot intervals, AOF fsync policy, replica lag, failover, and maxmemory eviction all change the answer.

I avoid mixing data with incompatible loss policies in one instance. Cache entries may be evicted. An idempotency record disappearing early can allow a duplicate charge. A distributed lock has timing and fencing concerns that a cache key does not. Separate instances or carefully separated policies are often easier to reason about than one shared Redis serving every purpose.

Cluster mode adds another constraint: multi-key operations and scripts require keys in the same hash slot. Key design is therefore part of choosing cluster mode, especially for libraries that coordinate several Redis keys.

BullMQ: enqueue is the beginning

A worker may receive a job more than once. It may finish the external side effect and crash before recording completion. Retries are useful only when the handler has an idempotency strategy for that window.

Job ID, business record ID, attempt count, and correlation ID should be searchable together. Exhausted jobs need an owner, an alert, and a replay process. A queue dashboard without a product-level failed status helps engineers while leaving the user stuck on processing.

RabbitMQ: durability is configured

A queue does not survive a broker restart merely because RabbitMQ created it. Durable queues, persistent messages, suitable replication, and publisher confirms are separate choices. Consumer acknowledgements cover another boundary: whether the broker may remove a delivered message.

Manual acknowledgement alone does not define retry. The consumer must decide whether to ack, reject, or nack, whether to requeue, and when to route a message through a dead-letter exchange. Without a retry limit, poison messages can loop indefinitely.

Prefetch is a workload decision, not a magic number. Small values improve fairness for slow jobs; larger values can raise throughput for short, consistent work. I start from the handler's concurrency and measure queue age and processing time before tuning it.

Choose the recovery procedure

Before adding any of these tools, I write down what happens during restart, partial outage, duplicate delivery, backlog growth, and operator replay. If the team cannot describe recovery, the architecture is not finished.

Using fewer tools reduces operational surface. The right reason to add another one is a failure or ownership model the current system cannot express cleanly, not a nicer client API.