Alex's blog

posts feed about

Kafka at the low end: how bad can it get?

17 Feb 2025

There is oft-quoted advice that Kafka does poorly as a job queue. I’ve experienced this myself, and I wanted to formalize it a bit.

I’ll use the common architecture of a Web application submitting background jobs to workers via Kafka (for example, to generate a PDF of some report). Except for the use of Kafka in this role, this is common in Web applications, and (speaking from experience!) when Kafka is already deployed, there is an impulse to use it instead of deploying yet-another queue system.

Note: when Queues for Kafka (KIP-932) becomes a thing, a lot of these concerns go away. I look forward to it!

What I want to characterize here is the worst-case “unfairness” of jobs being assigned to workers. There are many other reasons to not use Kafka as a job queue, but this unfairness is (in my view) the strongest reason. In most queues, you put work into the queue and every worker… well, works until all the work is done. It sound obvious, but that’s the raison d’être for these things! When (mis-)using Kafka as a queue, this is not the case: work can get unfairly assigned to one worker, even if other workers have nothing to do. So, how many jobs can one worker be assigned, before any other worker is given work? This can be worked out with this formula:

WorstCaseJobsPerConsumer = (Partitions / Consumers) * Producers

To work an example, say you have a topic with 16 partitions, because you would like to be able to scale up to 16 consumers at peak times, but, at your current load you only predict you need 4 consumers processing jobs. Further, say you have 5 producers (Web application servers, here - Gunicorn processes, Kubernetes pods, whatever) that receive an API call and put a job onto this Kafka topic. Imagine these Web workers are behind a load balancer which routes API calls in a round-robin fashion to each of those 5 Web workers. Pretty typical architecture right?

Plugging these numbers in, we get (16 / 4) * 5 == 20: that means, if you’re unlucky, the next 20 jobs coming along could all be routed to a single consumer, and that consumer has to churn away at those 20 jobs while its 3 counterparts will sit idle. How this would happen is by the following somewhat unlucky sequence of events:

This exact sequence of events is rare, but milder variations of this happen constantly when Kafka is used this way, at a low volume - such as only half, or three-quarters of your workers being busy, while the remainder are idle and there’s work queued, just sitting there.

To decide if this matters to your application, think about your peak periods and how many jobs might be created in that period, and what the latency expectations are for those jobs. If it’s a small internal application used by, say, 15 users, and they all (in the same instant) request 1 job that takes 5 minutes to run, then those 15 jobs can land on the same consumer and the queue takes 75 minutes to clear, leading to some of those users being very unhappy. This doesn’t always happen, but it can. On the other hand, if you have 200 users each requesting 1 job and those jobs take 1 second to run, these 200 jobs will be much more fairly distributed and all workers will be contributing to clearing that queue. So where, exactly, is this cutoff? As a rule of thumb, if you have at least WorstCaseJobsPerConsumer * Consumers jobs in-flight in your peak period (in the above example, this is 20 * 4 == 80 jobs), then you can be sure that all your workers are doing some work, because there are enough jobs to overcome the aforementioned worst-case behaviour. If there are fewer jobs than this, you run the risk that some workers will not be pulling their weight.

Please note that I’m completely ignoring varying job run times. That makes this problem significantly worse, because again, work is assigned to workers on a record-by-record basis, irrespective of how long those jobs take. A long job will block a short job and there’s nothing you can do about it.

I am not trying to say Kafka is a bad tool - what I am saying is it was not designed for such a low volume. It was designed for exactly the opposite (millions or billions of records) where a conventional single-node message broker simply cannot keep up. It strips away a lot of the very useful features of these conventional brokers in order to go faster. If you don’t need that speed, you are losing a lot in that trade-off!

In conclusion: the oft-quoted wisdom is right; Kafka is not a good job queue, especially not at particularly low volumes, at least until Queues for Kafka (KIP-932) comes along.