Kafka Lab
A consumer that spends 20 ms on each message cannot exceed 50 messages a second, however high you set its rate limit. This page is a simulated model of that ceiling — drag the controls and watch a backlog build, then clear it without touching the producer.
Nothing on this page is measured. There is no broker behind it, no producer, no consumer and no server-side work of any kind: the numbers below come from a closed-form model compiled to WebAssembly and run in your browser. The real lab is where these quantities get measured against an actual Kafka broker.
The ceiling
One consumer spending N milliseconds on a message tops out at 1000 ÷ N messages per second. That is arithmetic, not a tuning problem, and no rate limit changes it. The fixes are doing less work per message, or spreading the work across more consumers — which is what partitions are for. The consumer control below is there so you can prove that rather than take it on trust.
All figures below are simulated.
Loading the model…
What to try
- Raise the work per message to 40 ms. The ceiling halves to 25 while the producer keeps asking for 50, and the backlog climbs. The consumed figure sticks at the ceiling — that gap between what was asked for and what is achieved is the whole of backpressure.
- Now raise the rate limit. Drag the producer up and watch the backlog climb faster and the consumed rate not move at all. Turning the producer down would help, but that is not fixing the consumer.
- Put the producer back to 50, then drag Consumers to 3. The ceiling rises clear of the producer, the backlog turns over and it drains to zero. Note what fixed it: the producer is back where it started and the work per message is still where you left it in step 1 — adding consumers is what drained the queue.
- Keep adding consumers. Past 3 the ceiling stops moving. Kafka assigns whole partitions to group members, so a fourth consumer against a 3-partition topic gets nothing assigned and contributes nothing. At that point the fix is more partitions, not more processes.
What the model does and does not include
The model is three inputs and one line of integration: the ceiling is min(consumers, 3) × 1000 ÷ work, and the backlog integrates the difference between the producer rate and that ceiling, floored at empty. The cap is the topic's partition count: Kafka assigns whole partitions to group members, so beyond 3 consumers there is nothing left to assign and an extra one contributes nothing to the ceiling. That cap is why step 4 above stops paying off. There is deliberately no jitter, no random arrival process, no batching and no commit latency. Each of those would be defensible in a queueing model and each would make this page worse at its one job: halving the work should halve the ceiling, exactly, so you can check the arithmetic in your head. Variance invented for realism would be variance the page could not justify.
Run the real thing locally
The repository is a Docker Compose stack — a single-broker Kafka in KRaft mode, a Go producer and consumer, a control UI, Prometheus, Grafana and kafka-ui. One command brings it up, and the numbers there are measured rather than modeled.
git clone https://github.com/pigfox/kafka-lab
cd kafka-lab
./run.sh
Two things there work differently from the model above, and both are worth seeing. The services take their settings from a compacted Kafka topic rather than from an HTTP call, so the demo configures itself over its own bus and the settings survive a restart with no database. And the achieved rates are read from Prometheus while the backlog is read from the group coordinator — measured quantities, from a broker that is really running.