Stackbook Logo
messagingestablished · medium operational burden

Message Ordering

Also known as: ordered delivery, FIFO messaging

Intent

Guarantee that related messages are processed in the order they were produced, without serializing the whole system.

Problem

Parallel consumers, retries and multiple partitions reorder messages, so an update can be applied before the create it depends on. Global ordering would remove all parallelism.

Forces

  • •Ordering conflicts with parallelism
  • •Retries reintroduce earlier messages late
  • •Only some messages are actually related
  • •Producers may be multiple and concurrent

Solution

Overview

Ordering is cheap when scoped and expensive when global. The practical pattern is per-key ordering: everything about order 42 flows through one lane, while other orders use other lanes.

Sequence numbers add a safety net because infrastructure alone rarely covers every retry path.

Before building ordering, check whether you need it. Often a version check on the write, last-writer-wins by version, or commutative operations such as incrementing a counter remove the requirement. Ordering machinery is costly, so reserve it for cases where the sequence itself carries meaning, like state machines.

How it works

Producers key messages by aggregate id and stamp a monotonically increasing version. The consumer applies a message only when its version is the next expected one.

if msg.version == state.version + 1:
    apply(msg); state.version += 1
elif msg.version <= state.version:
    ack_and_ignore(msg)      # duplicate or stale
else:
    park(msg)                # gap: wait or fetch missing

When you must keep order, make retry behavior part of the design. Retrying in place preserves order but blocks the key. Moving the message to a retry queue unblocks others but lets later messages pass it, which is only safe if consumers tolerate that through version checks. Make the choice explicit per message type and document it for the team.

Pitfalls

  • Sending retries to a separate queue, which lets later messages overtake the failed one.
  • Using timestamps from different machines as an ordering source.
  • Assuming the broker orders across partitions.
  • Letting one poison message block a key forever with no alert.

Key takeaways

  • Choose the narrowest ordering key that works.
  • Add sequence numbers to detect gaps and stale data.
  • Failures must block or buffer, not skip silently.
  • Never rely on cross-partition order.

✓ When to Use

  • •State transitions of one entity must apply in sequence
  • •Updates overwrite earlier values
  • •Event sourced aggregates are projected from a stream

✗ When Not to Use

  • •Messages are independent commutative operations
  • •Consumers can resolve conflicts with timestamps or versions cheaply
  • •Throughput needs exceed one worker per key can supply

Pros

  • +Correct entity state
  • +Parallelism across keys remains
  • +Gap detection via sequence numbers

Cons

  • −Head-of-line blocking per key
  • −Hot keys limit throughput
  • −Multi-producer ordering needs coordination

Cost Profile

Infrastructure

Medium - partitioned broker

Operational

Medium - per-key stall handling

Cognitive

High - subtle reordering paths

Failure Modes

  • One failing message blocks all later messages for its key

  • Retry topic reorders messages

  • Two producers for one key interleave unpredictably

Real-World Examples

Alternatives

    Related Patterns

    • consumer-groups-partitioning
    • competing-consumers
    • dead-letter-queue

    Competency Domains

    distribution communicationdata state