How does message ordering work in a partitioned system like Kafka, and what breaks that ordering guarantee?
Kafka only promises order within a single partition, so the partitioning key you choose is the real mechanism deciding whether related events stay in sequence.
LEARN BY DOINGPut your intuition to work3 steps
How does message ordering work in a partitioned system like Kafka, and what breaks that ordering guarantee?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#Kafka only makes you one promise about order — read it carefully
Kafka's ordering guarantee is precise and easy to misstate: messages within a single partition are delivered to consumers in the exact order they were written. Nothing broader than that is promised. Across partitions — even within the same topic — there is no ordering relationship at all; two messages published a millisecond apart can land on different partitions and be consumed in either order, or even simultaneously by two different consumer instances.
That means ordering is really a partitioning problem, not a Kafka problem: your job is to route every message that has an ordering dependency on another message into the same partition, and let Kafka's per-partition guarantee do the rest.
#Partitioning by key: the standard mechanism
When you produce a record with a key, Kafka's default partitioner hashes the key and maps it deterministically to one of the topic's partitions (partition = hash(key) % numPartitions, roughly — the real implementation uses a sticky/murmur2 hash, but the effect is the same: same key, same partition, every time, as long as the partition count doesn't change).
Properties props = new Properties();props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true); // acks=all, in-order retries, no duplicate writesKafkaProducer<String, String> producer = new KafkaProducer<>(props);String orderId = "ORD-8891"; // the partitioning keyproducer.send(new ProducerRecord<>("order-events", orderId,"{\"type\":\"OrderCreated\",\"orderId\":\"ORD-8891\"}"));producer.send(new ProducerRecord<>("order-events", orderId,"{\"type\":\"OrderPaid\",\"orderId\":\"ORD-8891\"}"));producer.send(new ProducerRecord<>("order-events", orderId,"{\"type\":\"OrderShipped\",\"orderId\":\"ORD-8891\"}"));producer.flush();producer.close();
All three events use orderId as the key, so all three are hashed to the same partition. A consumer reading that partition sees them in exactly the order they were sent — OrderCreated, then OrderPaid, then OrderShipped — never any other order, regardless of how many other orders' events are interleaved on other partitions. That's the whole trick: pick a key that matches your ordering boundary (per order, per user, per account — whatever entity's events must stay in sequence), and let every event for that entity share a partition.
#What actually breaks the ordering guarantee
Four things, each for a different reason:
-
No key, or a key that doesn't match your ordering unit. With
key=null, Kafka's sticky partitioner spreads records across partitions to balance load — there's no ordering relationship between them at all. Keying by the wrong field (say,regionwhen you needed per-orderIdorder) still scatters a single order's events across whichever partitions the region happens to hash across. -
Changing the partition count on an existing topic.
hash(key) % numPartitionsdepends onnumPartitions. Add partitions to a live topic and the mapping for existing keys changes going forward — messages already written don't move, but new messages for the same key can land on a different partition than the old ones, breaking continuity between old and new events for that key. -
Producer retries without idempotence, when
max.in.flight.requests.per.connection > 1. If a producer has multiple batches in flight and an earlier batch fails and retries while a later batch succeeds, the retried batch can land after the later one — reordering records within the same partition. Settingenable.idempotence=true(the default in modern Kafka clients) closes this hole: the broker tracks a per-partition sequence number per producer and rejects/reorders retries correctly, guaranteeing in-order delivery even under retries. -
Consumer-side reordering from parallel processing. Even if Kafka hands your consumer records in perfect order, farming them out to a thread pool for parallel processing (to increase throughput) can let a later message finish and commit before an earlier one does. If downstream order matters, either process a partition's records single-threaded, or partition your in-process work queue by the same key so per-key order survives the fan-out.