Ordering vs Throughput - Trade-offs

In message systems, ordering (messages for the same logical stream processed in order) and throughput (messages per second) often conflict. Strict global ordering usually limits parallelism to a single consumer; partitioning by key gives ordering per key and scales. This article explains the tradeoff and practical patterns (partition by key, single consumer, accept reorder).

Overview

  • Global order: Every message is processed in the same order as produced. Typically one partition and one consumer → low throughput, single point of failure.
  • Order per key: Messages with the same key (e.g. user_id, order_id) go to the same partition and are processed in order by one consumer. Different keys can be processed in parallel → high throughput with order where it matters.
  • No order guarantee: Multiple partitions and consumers; messages can be processed out of order. Use when order does not matter (e.g. independent events) and you want maximum throughput.

Example

Example 1: Kafka — same key → same partition

Java
// All events for user 100 go to same partition → order preserved for that user
producer.send(new ProducerRecord<>("events", "user:100", event));
  • Throughput: multiple users in parallel. Order: per user. This is the usual pattern for “ordered per entity.”

Example 2: When you need global order

  • Example: single sequence of commands (e.g. state machine). Use one partition and one consumer (or consumer group with one partition). Throughput is limited by that single consumer.
  • Alternative: accept order only within a shard (e.g. per aggregate id in CQRS) and use partition by that id.

Example 3: Tradeoff table

GoalApproachThroughputOrder
Max throughputMany partitions, no keyHighNone
Order per keyPartition by key (user/order id)High (many keys)Per key
Global orderOne partition, one consumerLowGlobal

Core Mechanism / Behavior

  • Partition = order scope: In Kafka, order is guaranteed only within a partition. Producer chooses partition by key (or round-robin); consumer group assigns each partition to one consumer. So “same key → same partition” gives order per key.
  • Batching: Producer batching (linger.ms, batch.size) increases throughput but can reorder messages that are in different batches (usually same key still in same batch if key is set). Consumer fetches in order per partition.
  • Retry and DLQ: Retrying a failed message can break order (later messages already processed). To preserve order you may process one-by-one, or accept reorder and make logic idempotent.

Key Rules

  • Prefer order per key (partition by user_id, order_id, etc.) so you get both ordering for that entity and horizontal scaling. Use global order only when the business truly requires a single total order.
  • Design consumers to be idempotent so that occasional reorder or redelivery does not cause wrong state. Use message keys and partition assignment to get order where it matters.
  • Measure throughput and latency; add partitions and consumers until you hit order or resource limits, then decide whether to relax order or scale the single consumer (e.g. bigger instance).

What's Next

See Kafka Core Concepts for partitions and consumer groups. See Idempotency in Message Consumers for safe processing. See Kafka Delivery Semantics for at-least-once and redelivery.