
Why MVPs Break Under Real Load
Success is the failure mode. The shortcuts that got you to launch are exactly the ones that break when launch works.

The question is usually asked too early. A team notices requests timing out behind a slow third-party API, someone says "we need a queue", and the next two weeks go into evaluating brokers. The evaluation is the wrong first step, because all three options solve the immediate problem and the differences only start to matter later.
Here is the short version. BullMQ is a job queue for Node.js backed by Redis. RabbitMQ is a general-purpose message broker with rich routing. Apache Kafka is a distributed, durable event log. They are three different categories of tool that happen to overlap on one use case.
| BullMQ | RabbitMQ | Kafka | |
|---|---|---|---|
| Shape | Job queue | Message broker | Event log |
| Backed by | Redis | Erlang broker | Distributed log |
| Best at | Background jobs, retries, scheduling | Routing between many services | High-volume streams, replay |
| Message after consumption | Gone | Gone | Retained, replayable |
| Operational weight | Low, if you already run Redis | Medium | High |
| Natural fit | One Node.js codebase | Polyglot services | Analytics, event sourcing |
The row people skip is the fourth. In BullMQ and RabbitMQ a message is consumed and gone. In Kafka it stays in the log, so a new consumer can read history from the beginning. If you ever need to replay six months of events into a new service, that is a Kafka property and you cannot retrofit it.
If you are a Node.js team, you already run Redis, and the problem is "slow work is blocking requests", BullMQ is almost always correct. It gives you retries with backoff, delayed and repeatable jobs, priorities, rate limiting and concurrency control, and you can be running in an afternoon.
This is what we reached for on an ecommerce SaaS whose MVP was stalling under concurrent load, described in scaling a SaaS with event-driven architecture. Orchestrators enqueue jobs, separate worker pools consume them, and one degraded marketplace API stops being able to take down billing.
The honest limitation: BullMQ's durability is Redis's durability. Redis persistence is good but it is not a replicated commit log, and if losing a job would mean losing money you owe someone, size that risk deliberately rather than assuming.
RabbitMQ becomes the right answer when routing gets interesting and the producers and consumers are not all the same language. Topic exchanges, fanout, per-queue policies and dead-letter exchanges are first-class, and clients exist for every runtime.
If you have a Python service that needs to react to events emitted by a Node service and a Go service, RabbitMQ is a more natural fit than bolting all three onto a Redis-backed job queue designed for one ecosystem.
Kafka is a genuinely different tool and carries real operational cost. It earns that when you need durable, replayable, high-throughput streams — event sourcing, analytics pipelines, multiple independent consumers of the same stream, or an audit log that must survive.
The failure mode is choosing Kafka for a workload that is really background jobs. You get a system your team cannot debug at 3am, in exchange for guarantees you were not using. If your throughput is in the hundreds of messages per second and nothing needs replaying, Kafka is a liability rather than an upgrade.
Whichever you pick, moving work out of the request introduces the same two problems, and getting them wrong hurts more than picking the "wrong" broker.
Jobs will run more than once. A worker dies mid-task, a pod is rescheduled, an upstream times out after having actually succeeded. All three systems give you at-least-once delivery in practice, so a retry must be safe. Key each job by something stable, record completion, and check before acting.
Workers waiting on a slow API use almost no CPU, so CPU-based autoscaling never triggers while the backlog grows. Scale on queue depth instead: KEDA exposes queue length as a metric to the Kubernetes horizontal pod autoscaler, which turns a backlog into a cost line rather than an incident.
If the real problem is that your product is buckling under load rather than which broker to adopt, why MVPs break at scale covers the pattern, and scale and re-architecture is the work we do about it.
Yes, for background jobs in a Node.js system. BullMQ gives you retries with backoff, delayed and repeatable jobs, priorities, rate limiting and concurrency control, and it runs on Redis you are probably already operating. Its durability is Redis's durability, so if losing a job would mean losing money, size that risk deliberately rather than assuming.
BullMQ is a job queue: a message is consumed and gone. Kafka is a durable event log: messages are retained, so a new consumer can replay history from the beginning. Use a queue when work should vanish once done, and a log when someone might need to read the same events again later. Kafka also carries far higher operational cost.
When you need replay, event sourcing, or several independent consumers reading the same high-volume stream — not simply because throughput grew. RabbitMQ handles substantial load. The trigger is a change in requirements, specifically needing messages retained after consumption, rather than a message-per-second threshold.
Almost always because it scales on CPU. Workers waiting on a slow external API consume very little CPU, so utilisation stays low while the backlog grows. Scale on queue depth instead, using KEDA to expose queue length to the Kubernetes horizontal pod autoscaler.