Skip to main content

Kafka, Inside-Out

Editorial

The one guarantee everybody quotes

Ask anyone what Kafka promises and you get the same sentence: ordering is guaranteed within a partition. It is in the documentation, it is in every tutorial, and it is true.

You already have the parts. Queue Models: Broker vs Log gave you the offset and the difference between competing for a message and reading your own copy. The Kafka codelab had you create a partitioned topic, watch keys pick partitions, run a consumer group and rewind. Delivery Semantics settled at-most, at-least and exactly-once. Assume all of it.

This lesson is about the four words nobody finishes the sentence with: within a partition — and the partition is not a stable place.

Where a key actually lives, and why that is a function and not a home

When you send a record with a key, Kafka does not look up where that key went last time. There is no directory. It computes:

partition = murmur2(key) & 0x7fffffff  %  number_of_partitions

Read the right-hand end of that expression. The partition a key lands in depends on the number of partitions the topic has at the moment you send it. Nothing about the key changed; nothing about the record changed. Change the divisor and you change the answer.

And the number of partitions is the one knob everybody eventually turns, because it is the only way to add consumers.

Turning the knob, measured

Sixty keys — user-1 through user-60 — produced to a topic with three partitions, so every key has a history. Then the topic is grown to six partitions, exactly as a team would do it on a Tuesday afternoon to keep up with load. The same sixty keys are produced again.

The central measurement of the Kafka lesson, drawn as a before and after. Across the top, inside a blue band, sits the formula that produces the whole effect: partition equals murmur2 of the key, modulo the number of partitions. Beneath it, a row labelled before shows three green boxes for three partitions, each drawn with a small fill bar and carrying its key count, eighteen, nineteen and twenty-three. Below that, a row labelled after shows six blue boxes for six partitions, carrying eight, eight, fourteen, ten, eleven and nine. The same sixty keys produced the two rows; only the divisor changed. A red panel below records what that cost. Its heading states that thirty of the sixty keys now write to a different log from their own history, and six named examples are drawn as small journeys with an arrow between the old partition and the new one: user-3 from partition two to partition five, user-5 from one to four, user-6 from two to five, user-8 from zero to three, user-9 from two to five, and user-11 from one to four. Each arrow is a history being cut in half, because two partitions have no ordering relationship between them at all. A closing amber band gives the part that makes this dangerous rather than merely surprising: there was no error, no consumer lag and no alert, so the per-key ordering the design depended on simply stopped being true, and Kafka states that it will not redistribute the data to repair it.
partitionkeys beforekeys after
0188
1198
22314
310
411
59

30 of the 60 keys — exactly half — now land in a different partition from the one holding their own earlier events.

key         was in   now in
user-3           2        5
user-5           1        4
user-6           2        5
user-8           0        3
user-9           2        5
user-11          1        4

Look at what that means for user-3. Everything that account did before Tuesday is in partition 2. Everything it does after is in partition 5. Those are two independent logs with no ordering relationship whatsoever — not a weak one, none. A consumer reading both cannot tell you which came first, and no amount of care on the consumer side can recover it, because the information was never recorded.

Nothing failed. No alert fired. The producer kept returning success, the lag stayed at zero, and the per-key ordering the whole design rested on quietly stopped being true for half the keys.

Kafka is entirely upfront about this. From its own documentation:

"this partitioning will potentially be shuffled by adding partitions but Kafka will not attempt to automatically redistribute data in any way"

and the remedy it offers is not a repair — it is a rebuild: "you will have to manually copy data from the old low partition topic to a new higher partition topic."

And there is no way back

The obvious instinct is to undo it. You cannot.

asking for 3 partitions again  ->  InvalidPartitionsError

The partition count is a ratchet. It goes up, it never comes down, and going up is the operation that breaks per-key ordering for every key already in the topic. That is a genuinely unusual property for a number in a config file, and it is why the partition count deserves the same care as a shard key rather than the casual treatment it usually gets.

It also explains why the standard advice is to over-provision partitions at creation. That advice is real, and this is the reason for it — but it is not free either, which is the next measurement.

Why anyone turns the knob at all

Nobody adds partitions for fun. They add them because a consumer group cannot keep up, and the partition count is the only lever that exists.

Five consumers in one group, on a topic with three partitions. This is the broker's own view, not the clients' opinion of themselves:

The collision at the heart of the Kafka lesson, drawn as two panels side by side. The left panel is headed three partitions, five consumers, and notes that the assignment was read from the broker rather than from the clients. Beneath it, five member rows are drawn: the first three are outlined in green and each reads one partition, while the fourth and fifth are outlined in red and read idle. A caption states the rule that produces this, which is that a partition is never shared between members of a group, so two of the five machines sit doing nothing at all. The right panel is headed so you raise the count and shows the partition count as a ladder of three boxes reading three, six and twelve. Green arrows point downward from each box to the next, labelled allowed, because the count may always be increased. Beside the lower two boxes, red crosses on upward paths mark the reverse direction as refused, and the panel closes with the error the broker actually returns, InvalidPartitionsError. Read together the two panels make one point, which the closing red band states: this is a single knob that both raises the consumer ceiling and splits the keys, and it only turns one way, so it has to be decided before the first message is ever produced.
member 1   1 partition
member 2   1 partition
member 3   1 partition
member 4   nothing — idle
member 5   nothing — idle

Two of the five consumers were assigned nothing at all. A partition is never shared between members of a group, so the partition count is a hard ceiling on how far that group can scale. Adding machines past it changes nothing; they join, they are given no work, and they sit there heartbeating.

So the two facts sit on top of each other, and this is the trap the lesson exists for:

The only lever that raises your consumer ceiling is the same lever that permanently breaks per-key ordering.

Which is why the answer to we need more throughput is never just add partitions. It is add partitions, and here is what happens to every key that has a history.

Move the counts yourself

This runs Kafka's actual partitioner — the same murmur2 arithmetic the broker runs — so the keys move exactly where a real cluster would put them. Type your own keys if you like.

Kafka's real partitioner, running in your browser: murmur2 of the key, modulo the partition count. Type your own keys, move the counts, and watch which histories split.

Three things worth doing on purpose. Run the default 3 to 6 and confirm you get 30 of 60, which is the number measured on the live broker. Then try 3 to 4, or 5 to 7, and watch that there is no safe multiple — doubling is not gentler than any other jump. Then set them equal and watch nothing move at all, which is the only configuration that preserves every key's history: the one where you did not turn the knob.

What ordering you actually have

It is worth being exact about what survives, because the useful version of this knowledge is a precise claim rather than a worry. Three keys, five events each, one topic of four partitions:

alice  always landed in partition 1
bob    always landed in partition 2
carol  always landed in partition 2

order inside a partition   : exact
order across partitions    : none

what a consumer sees:
  alice#0 alice#1 alice#2 alice#3 alice#4 bob#0 carol#0 bob#1 carol#1 ...

Two things in that output are worth pausing on. First, bob and carol share a partition — you do not get one partition per key, you get a hash, and unrelated keys collide constantly. Their events interleave, and that is fine, because they were never related.

Second, the guarantee is real and it is narrow: per key, per partition, for as long as that key keeps hashing to the same partition. Every one of those three conditions is load-bearing, and the third one is the one nobody says out loud.

The other moving part: what acks=all actually counts

The partition count is the surface that moves under your feet on purpose. There is a second one that moves on its own, and it decides durability rather than ordering.

acks=all sounds like all replicas. It means all replicas currently in sync — and the in-sync set is a quantity the cluster maintains by itself, shrinking whenever a follower falls behind. The number that actually decides whether your write is safe is min.insync.replicas.

A note on where this number comes from. Everything above was measured on a live broker. This part is documented rather than measured here, and it is worth saying why: the experiment was attempted on a single broker and abandoned. A topic was configured with min.insync.replicas=2 against one replica — the setting applied, confirmed with kafka-configs.sh — and yet both a console producer with acks=all and a client with acks=-1 were accepted. A single-node cluster will not reproduce the refusal, so rather than publish a number that did not happen, here is what Kafka documents:

  • Below the threshold with acks=all, the producer raises NotEnoughReplicas or NotEnoughReplicasAfterAppend.
  • The recommended pairing is replication factor 3 with min.insync.replicas 2, which leaves exactly one spare.
  • unclean.leader.election.enable allows a replica from outside the in-sync set to become leader as a last resort, and Kafka states plainly that it may result in data loss.

The consequence is worth having ready, because it is a favourite interview question and it sounds like a paradox. Set replication factor 3 and min.insync.replicas 3, lose one broker out of three, and the cluster is healthy while the topic is read-only — every durable write refused, nothing actually broken. Set it to 2 and the same failure is a shrug. One number, and it decides whether a single node leaving is an incident.

Why you would pick it, and what to say in the room

Reach for it when you need a durable, replayable, ordered-per-key record that several independent consumers read at their own pace — the shape Queue Models: Broker vs Log argued for. Do not when what you actually want is a work queue with per-message acknowledgement and redelivery, when the consumer count needs to scale freely with load, or when strict global ordering matters, because Kafka has never offered that and one partition is not an answer at scale.

The thing that separates a candidate who has read about Kafka from one who has run it is not knowing that ordering is per-partition. Everybody says that. It is knowing that the partition is a function of a number that changes.

if they asksay
We need more consumer throughput — add partitions?That is the only lever, and it is one-way. Measured: going from 3 to 6 moved half the keys to a different log from their own history, and Kafka will not redistribute or let you shrink back. If per-key order matters, the safe route is a new topic and a controlled migration.
How many partitions should we start with?More than you need, because the count only goes up and going up is the destructive direction. Bound it by the consumer ceiling you want and the per-partition overhead you can afford.
Is Kafka ordered?Per key, per partition, while that key keeps hashing to the same partition. Across partitions there is no ordering at all.
Does acks=all mean it is safe?It means all IN-SYNC replicas, and that set shrinks on its own. min.insync.replicas is the number that decides — and if you set it equal to the replication factor, losing one broker makes the topic read-only.
Why is one consumer idle?It has more members than partitions. A partition is never shared, so those members will stay idle until the partition count goes up.

And the sentence to keep if you keep only one: Kafka's guarantees are scoped to a partition, and the partition is a function of a number that moves. Ordering, consumer scale and durability are three faces of that one fact — which is why they are all decided long before the first message is produced, in the arguments to the command that creates the topic.