Series MapLesson 15 / 54
Focus mode active/Press Alt+Shift+R to toggle/Esc to exit
Build CoreOrdered learning track

Ordering Guarantees

Ordering guarantees RabbitMQ untuk enterprise Java/JAX-RS systems: queue FIFO, single consumer, multiple consumer ordering risk, prefetch, redelivery, requeue, priority queue, retry queue, quorum queue, stream ordering, per-aggregate ordering, message group pattern, single active consumer, dan ordering review checklist.

9 min read1633 words
PrevNext
Lesson 1554 lesson track11–29 Build Core
#rabbitmq#ordering#fifo#single-active-consumer+11 more

Ordering Guarantees

1. Core idea

Ordering guarantee menjawab pertanyaan:

Jika message A dipublish sebelum message B, apakah consumer pasti memproses A sebelum B?

Jawaban senior engineer hampir selalu:

Tergantung scope ordering-nya.

RabbitMQ queue secara konseptual menyimpan message dalam urutan enqueue. Tetapi production ordering bukan hanya urutan queue.

Ordering aktual dipengaruhi oleh:

publisher concurrency
exchange routing
queue type
queue count
consumer count
prefetch
manual ack
nack/requeue
redelivery
retry topology
priority queue
single active consumer
consumer processing model
PostgreSQL transaction ordering
Kubernetes replica scaling
broker failover
manual replay

Kesalahan umum adalah menganggap:

RabbitMQ queue is FIFO
therefore my business workflow is ordered

Itu terlalu sederhana.

Dalam enterprise Java/JAX-RS system, ordering harus didefinisikan sebagai business property, bukan hanya broker property.

Contoh yang benar:

For each orderId, state transition messages must be applied in sequence.
Across different orderId values, parallel processing is allowed.

Contoh yang salah:

All order messages must always be ordered globally.

Global ordering biasanya mahal, rapuh, dan membatasi throughput.


2. Why ordering exists

Ordering penting ketika message mewakili perubahan state.

Contoh dalam CPQ/order management:

QuoteCreated -> QuotePriced -> QuoteApproved -> QuoteConvertedToOrder
OrderSubmitted -> OrderValidated -> OrderDecomposed -> OrderSentToFulfillment
OrderFalloutRaised -> OrderFalloutResolved

Jika message diproses out-of-order:

OrderApproved processed before OrderCreated
FulfillmentStarted processed before OrderValidated
PriceAdjustment processed after QuoteAccepted
Cancellation processed before Submission

maka sistem bisa menghasilkan:

invalid state transition
stale projection
incorrect downstream command
duplicate fulfillment
false customer notification
manual repair
incident

Ordering bukan sekadar masalah teknis. Ordering adalah business invariant.


3. RabbitMQ ordering baseline

Baseline paling sederhana:

one publisher
one exchange
one queue
one consumer
manual ack after processing
no redelivery
no retry queue
no priority
no parallel processing

Dalam kondisi ini, processing order relatif mudah dipahami.

flowchart LR P[Publisher] --> E[Exchange] E --> Q[Queue] Q --> C[Single Consumer] M1[Message 1] --> Q M2[Message 2] --> Q M3[Message 3] --> Q

Tetapi production jarang sesederhana ini.

Begitu ada:

multiple publishers
multiple consumers
prefetch > 1
async processing inside consumer
retry/DLQ
manual replay
priority
multiple queues
Kubernetes horizontal scaling

maka urutan business effect dapat berubah.


4. Queue ordering vs processing ordering

Ada tiga level ordering yang harus dibedakan.

4.1 Enqueue ordering

Urutan message masuk ke queue.

Q contains: M1, M2, M3

4.2 Delivery ordering

Urutan broker mengirim delivery ke consumer.

Consumer receives: M1, M2, M3

4.3 Commit ordering

Urutan business effect benar-benar committed ke PostgreSQL atau sistem downstream.

DB commits: M2, M1, M3

Problem senior engineer biasanya bukan hanya delivery order, tetapi commit order.

Consumer bisa menerima M1 lebih dulu, tetapi M2 selesai lebih cepat karena:

processing time berbeda
external service latency berbeda
thread pool parallelism
DB lock contention
retry M1
consumer crash setelah M1 diproses tetapi sebelum ack

Karena itu, ordering harus dikunci di tempat yang benar:

broker topology
consumer concurrency
aggregate-level state transition
PostgreSQL constraint/version check
idempotency logic

5. When RabbitMQ preserves useful order

RabbitMQ dapat memberi ordering yang berguna ketika constraint berikut terpenuhi:

single logical queue for the ordered stream
single active consumer or one consumer only
consumer processes synchronously/sequentially
prefetch is controlled
ack happens after processing
no requeue loop
no priority queue
no delayed retry topology that changes order
no manual replay mixed into live traffic
publisher order is deterministic

Jika salah satu constraint dilanggar, ordering masih mungkin cukup baik untuk use case tertentu, tetapi tidak boleh diasumsikan strict.


6. Single queue ordering

Single queue memberi satu tempat serialization.

flowchart LR P1[Publisher A] --> E[Exchange] P2[Publisher B] --> E E --> Q[order.lifecycle.queue] Q --> C[Consumer]

Tetapi single queue hanya menyelesaikan sebagian masalah.

Jika dua publisher publish concurrently, urutan arrival ke broker bisa berbeda dari urutan business event di database.

Contoh:

T1: Service A commits OrderValidated
T2: Service B commits OrderCancelled
T3: Service B publishes OrderCancelled
T4: Service A publishes OrderValidated

Queue order menjadi:

OrderCancelled
OrderValidated

Padahal business timestamp bisa mengharapkan sebaliknya atau justru cancellation harus menang.

Pelajaran:

Queue order is not automatically domain order.

Domain order harus didukung oleh:

event version
aggregate version
state transition guard
createdAt/publishedAt semantics
source-of-truth state read

7. Single consumer ordering

Jika satu queue dikonsumsi satu consumer dan consumer memproses satu message per waktu, processing order lebih mudah dijaga.

queue -> consumer -> process -> commit -> ack -> next delivery

Kelebihan:

simple ordering model
easier debugging
less duplicate interleaving
safer for strict state transition

Kekurangan:

throughput terbatas
slow message blocks following messages
poison message can stop progress
horizontal scaling sulit
latency tail tinggi

Cocok untuk:

low-volume critical ordering flow
per-aggregate serialization worker
control-plane commands
state machine transition executor

Tidak cocok untuk:

high-volume independent tasks
event fanout besar
IO-bound parallel processing
bulk integration jobs

8. Multiple consumers ordering risk

Multiple consumers meningkatkan throughput, tetapi merusak strict processing order.

flowchart LR Q[Queue] --> C1[Consumer 1] Q --> C2[Consumer 2] Q --> C3[Consumer 3]

Contoh:

M1 delivered to C1
M2 delivered to C2
M3 delivered to C3

C2 commits first
C3 commits second
C1 commits last

Delivery order:

M1 -> M2 -> M3

Commit order:

M2 -> M3 -> M1

Jika message berbeda aggregate, ini biasanya aman.

Jika message aggregate yang sama, ini bisa fatal.

Rule praktis:

Multiple consumers are safe only if messages are independent or consumer logic protects ordering at aggregate level.

9. Prefetch ordering impact

Prefetch menentukan berapa banyak unacked delivery boleh outstanding.

Jika prefetch tinggi, broker bisa mengirim beberapa message ke consumer yang sama sebelum message pertama selesai.

prefetch = 10
consumer receives M1..M10
consumer processes them in internal thread pool
M7 commits before M1

Prefetch tinggi aman jika:

processing order tidak penting
consumer processing tetap sequential
message independent
idempotency kuat

Prefetch tinggi berbahaya jika:

consumer memproses parallel
message satu aggregate
state transition harus sequential
retry/requeue bisa terjadi
DB lock contention tinggi

Untuk strict ordering:

prefetch = 1
single active consumer
no internal parallelism

Namun ini bukan default terbaik untuk semua flow. Untuk work queue independen, prefetch > 1 bisa sangat membantu throughput.


10. Redelivery ordering impact

Redelivery terjadi saat message sudah delivered tetapi belum acknowledged, lalu broker harus mengirim ulang.

Penyebab:

consumer crash
connection lost
channel closed
nack/reject with requeue
consumer timeout
node failure/failover

Contoh:

M1 delivered
M2 delivered
M1 processing fails and is requeued
M2 succeeds
M3 succeeds
M1 redelivered later

Business effect bisa menjadi:

M2 -> M3 -> M1

Redelivery membuat ordering tidak lagi murni FIFO.

Rule penting:

If redelivery is possible, out-of-order handling must be possible.

Karena at-least-once delivery selalu membuka peluang redelivery, consumer state transition harus robust.


11. Requeue ordering impact

basic.nack(requeue=true) atau basic.reject(requeue=true) terlihat sederhana, tetapi production impact-nya besar.

Masalah utama:

message bisa kembali ke queue
message bisa segera dikirim lagi
message bisa mendahului atau tertinggal dari message lain tergantung queue state
redelivery loop bisa terjadi
consumer lain bisa memproses message berikutnya lebih dulu

Anti-pattern:

try {
    handle(delivery);
    channel.basicAck(tag, false);
} catch (Exception e) {
    channel.basicNack(tag, false, true); // dangerous as default
}

Kenapa berbahaya:

transient failure bisa berubah menjadi hot loop
poison message terus diproses ulang
ordering rusak
broker dan downstream service tertekan
log meledak
DLQ tidak pernah dipakai

Lebih aman:

classify failure
increment retry metadata
send to retry topology or DLQ
nack/reject without blind requeue

12. Priority queue ordering impact

Priority queue secara sengaja mengubah FIFO.

Jika priority digunakan, maka message berpriority tinggi dapat diproses sebelum message yang lebih dulu masuk.

Cocok untuk:

operational task prioritization
customer-impacting repair task
urgent notification
control command

Berbahaya untuk:

strict state transition
per-order lifecycle
financial adjustment sequence
event projection that assumes chronological order

Priority queue juga dapat menciptakan starvation:

low priority messages never get processed because high priority traffic keeps arriving

Checklist sebelum menggunakan priority:

Apakah order memang boleh dilanggar?
Apakah starvation acceptable?
Apakah priority count dibatasi?
Apakah metrics per priority tersedia?
Apakah retry/DLQ behavior sudah diuji?

13. Retry queue ordering impact

Retry topology hampir selalu memengaruhi ordering.

Contoh TTL retry:

flowchart LR Q[main queue] --> C[consumer] C -->|failure| R[retry queue with TTL] R -->|TTL expires| E[main exchange] E --> Q

Scenario:

M1 fails and goes to retry for 5 minutes
M2 succeeds immediately
M3 succeeds immediately
M1 returns later

Processing order:

M2 -> M3 -> M1

Karena itu, delayed retry tidak kompatibel dengan strict total order.

Jika business membutuhkan per-aggregate order, opsi lebih aman:

pause aggregate processing until failed message resolved
store sequence gap in DB
route by aggregate shard
use single active consumer per shard
put failed aggregate into repair state
separate ordered command executor from unordered task queue

14. DLQ and manual replay ordering impact

DLQ menyimpan message yang gagal permanen atau melebihi retry.

Manual replay dari DLQ ke main queue sering dilakukan saat incident recovery.

Risiko:

old message enters live stream
old state transition applies after newer state
duplicate external command sent
customer notification repeated
projection rollback-like effect

Manual replay harus punya guard:

idempotency check
aggregate version check
current state validation
operator approval
replay batch limit
dry-run mode if possible
clear audit trail

Replay bukan hanya operasi queue. Replay adalah operasi business.


15. Quorum queue ordering considerations

Quorum queue memberi durability dan replication berbasis leader/follower model.

Ordering tetap harus dipahami dari sisi queue dan consumer.

Hal yang perlu diperhatikan:

leader failover can cause redelivery
unacked messages can be delivered again
consumer must be idempotent
poison handling/delivery limit can move messages out of normal flow
throughput/latency profile differs from classic queue

Quorum queue membantu data safety, tetapi tidak menghilangkan kebutuhan idempotency atau ordering guard.

Rule:

Quorum improves replicated durability, not business-level exactly-once ordering.

16. RabbitMQ Stream ordering considerations

RabbitMQ Stream berbeda dari queue biasa.

Stream lebih cocok ketika:

retention matters
replay matters
offset matters
high-throughput append matters
consumers need independent positions

Ordering biasanya dipahami per stream atau per partition/super stream.

Jika stream dipartisi:

global order across partitions is not guaranteed
per-partition order is the useful guarantee

Untuk domain seperti order management:

partition key should usually be aggregate id, order id, quote id, customer id, or tenant+aggregate id

Jika key salah, message satu aggregate bisa masuk partition berbeda dan ordering rusak.


17. Per-aggregate ordering

Per-aggregate ordering adalah model yang paling realistis untuk enterprise domain.

Daripada meminta global order:

all messages ordered globally

lebih baik:

messages for same orderId ordered
messages for different orderId can run in parallel

Contoh:

order-123: Created -> Validated -> Decomposed
order-456: Created -> Cancelled

Keduanya boleh parallel.

Pattern umum:

route by aggregate key
shard queue by aggregate hash
single active consumer per shard
aggregate version in message
PostgreSQL optimistic locking
state transition guard

18. Message group pattern

RabbitMQ AMQP 0-9-1 tidak memiliki konsep message group bawaan seperti beberapa broker lain.

Namun message group bisa dirancang dengan kombinasi:

routing key contains aggregate group
consistent hash exchange plugin if used
fixed shard queues
single active consumer per shard
consumer-side per-key serialization
DB-level version guard

Contoh topology:

exchange: order.command.x
routing key: tenantA.order.123.command.submit
hash key: orderId
queues:
  order.command.shard.00.q
  order.command.shard.01.q
  order.command.shard.02.q

Important:

Message group is a design pattern, not magic.

Harus diverifikasi di topology dan code.


19. Single Active Consumer

Single Active Consumer membantu memastikan hanya satu consumer aktif menerima message dari queue pada satu waktu.

Manfaat:

stronger queue-level processing order
safe failover to standby consumer
less accidental parallel processing
useful for ordered command executor

Trade-off:

throughput limited by one active consumer
standby consumers idle
slow processing blocks queue
poison message can stop progress
scaling requires sharding into multiple queues

Pattern production:

N shard queues
single active consumer enabled per shard
multiple pod replicas can subscribe
only one active consumer per shard at a time
flowchart LR X[order.command.exchange] --> Q0[shard 00 queue - SAC] X --> Q1[shard 01 queue - SAC] X --> Q2[shard 02 queue - SAC] Q0 --> C0[active consumer] Q1 --> C1[active consumer] Q2 --> C2[active consumer]

Ini memberi kompromi:

ordered per shard
parallel across shards
controlled failover

20. Publisher-side ordering

Ordering tidak hanya masalah consumer.

Publisher bisa merusak urutan jika:

multiple threads publish events for same aggregate
multiple service instances publish related messages
outbox poller mengambil rows tanpa deterministic order
publisher retry publishes older event after newer event
confirm timeout causes duplicate publish later

Outbox harus memperhatikan ordering:

SELECT *
FROM outbox
WHERE status = 'NEW'
ORDER BY aggregate_id, aggregate_version
FOR UPDATE SKIP LOCKED
LIMIT 100;

Namun query seperti itu belum otomatis benar. Jika banyak poller berjalan paralel, aggregate yang sama bisa diproses oleh poller berbeda.

Untuk ordering-sensitive outbox:

lock per aggregate
partition outbox polling by aggregate hash
publish in aggregate_version order
prevent publishing version N+1 before N
mark blocked aggregate if version N fails

21. PostgreSQL/MyBatis/JDBC ordering guard

Broker ordering tidak cukup. PostgreSQL harus ikut menjaga state correctness.

Guard yang umum:

21.1 Aggregate version

UPDATE orders
SET status = 'VALIDATED', version = version + 1
WHERE order_id = #{orderId}
  AND version = #{expectedVersion}
  AND status = 'SUBMITTED';

Jika affected rows = 0:

message duplicate
message stale
message out-of-order
invalid state transition

Consumer tidak boleh asal ack tanpa klasifikasi.

21.2 Processed message table

INSERT INTO processed_message(message_id, consumer_name, processed_at)
VALUES (#{messageId}, #{consumerName}, now())
ON CONFLICT DO NOTHING;

21.3 State transition table

order_state_transition
- order_id
- from_state
- to_state
- message_id
- event_version
- created_at

Membantu audit ordering.

21.4 Unique command key

tenant_id + command_id
order_id + command_type + idempotency_key

Mencegah duplicate command menghasilkan duplicate side effect.


22. Java/JAX-RS backend implications

Dalam JAX-RS service, ordering concern sering muncul saat HTTP request diterjemahkan menjadi command message.

Contoh:

POST /orders/{id}/submit
POST /orders/{id}/cancel
POST /orders/{id}/amend

Jika request masuk hampir bersamaan:

submit and cancel race
cancel and amend race
approval and price recalculation race

API layer perlu memutuskan:

Apakah request diterima async tanpa validasi state kuat?
Apakah service layer menulis command row dulu?
Apakah command sequence ditentukan di DB?
Apakah RabbitMQ hanya membawa command id?
Apakah consumer membaca current state sebelum execute?

Pattern yang lebih aman:

JAX-RS validates basic request
service layer creates command record with idempotency key
DB transaction commits command + outbox
publisher emits command message
consumer loads command and aggregate state
consumer applies guarded transition
ack after commit

23. Microservices and distributed consistency

Distributed system tidak punya satu global clock yang aman untuk business ordering.

Message timestamp bisa berbeda karena:

clock skew
network delay
publisher retry
outbox lag
consumer lag
cross-region latency
manual replay

Karena itu, ordering antar service harus berbasis:

aggregate version
causation id
correlation id
state machine guard
source-of-truth read
idempotency
contracted event semantics

Event consumer tidak boleh mengasumsikan:

If I receive Event B after Event A, then B is newer in business sense.

Lebih aman:

Event contains aggregateVersion.
Consumer applies only if version advances expected state.
Otherwise classify as duplicate, stale, gap, or conflict.

24. Kubernetes scaling impact

Kubernetes dapat merusak ordering secara tidak sengaja melalui scaling.

Contoh:

replicas: 5

Jika semua pod consume queue yang sama:

consumer count = 5
processing order is parallel
strict queue order is gone at commit level

Rolling update juga dapat menyebabkan:

pod receives message
pod starts shutdown
message not acked
message redelivered to new pod
newer message already processed by another pod

Checklist Kubernetes:

Apakah flow ordering-sensitive?
Apakah HPA boleh scale consumer?
Apakah preStop drain benar?
Apakah graceful shutdown menunggu in-flight message?
Apakah consumer replicas dikontrol?
Apakah single active consumer digunakan?
Apakah queue dishard untuk parallel ordered processing?

25. AWS/Azure/on-prem/hybrid impact

Ordering juga dipengaruhi deployment environment.

25.1 Cloud managed broker

Perlu cek:

broker failover behavior
maintenance window
node restart behavior
client reconnect behavior
queue type support
observability around redelivery after failover

25.2 Kubernetes self-managed

Perlu cek:

pod disruption
statefulset rolling restart
storage latency
network partition
anti-affinity
quorum queue leader movement

25.3 On-prem/hybrid

Perlu cek:

WAN latency
firewall interruptions
certificate expiry
cross-site broker connection
manual replay after outage
clock skew between sites

Ordering-sensitive flows harus diuji terhadap failover, bukan hanya happy path.


26. Ordering failure modes

Failure modeSymptomLikely causeRisk
Out-of-order state transitionDB update rejected or invalid stateMultiple consumers, retry, redeliveryBusiness inconsistency
Duplicate older eventSame message processed againRedelivery after crashDuplicate side effect
Stale projectionRead model shows old stateEvent processed lateCustomer/API confusion
Retry inversionLater messages processed before failed earlier oneDelayed retry topologyState conflict
Replay inversionDLQ message replayed into live flowManual replayRegression-like effect
Priority starvationLow priority never processedPriority queue misuseSLA violation
Shard misroutingSame aggregate processed in different queueBad routing key/hashBroken per-aggregate order
Poller inversionOutbox publishes newer before olderParallel pollersEvent inconsistency

27. How to detect ordering failures

Observable signals:

invalid state transition count
optimistic lock conflict rate
stale event discard count
aggregate version gap count
duplicate message count
redelivery rate
DLQ count for state conflict
manual replay audit
consumer processing latency by aggregate
outbox lag by aggregate

Log fields required:

messageId
correlationId
causationId
aggregateId
aggregateVersion
routingKey
queue
consumerName
redelivered
retryCount
xDeathCount
currentState
expectedState
transitionResult

Without these fields, ordering debugging becomes guesswork.


28. Debugging ordering issue

Use this sequence.

Step 1: Identify aggregate

Which quoteId/orderId/customerId is affected?

Step 2: Build message timeline

Collect:

createdAt
publishedAt
enqueuedAt if available
deliveredAt
processingStartedAt
committedAt
ackedAt
redelivered flag
retry count

Step 3: Compare business version

Expected aggregate version sequence?
Actual processed sequence?
Missing version?
Duplicate version?
Stale version?

Step 4: Inspect topology

Was there one queue or many?
How many consumers?
Prefetch?
Retry queue?
Priority?
Manual replay?
Single active consumer?

Step 5: Inspect deployment activity

Was there rollout?
HPA scale event?
Broker failover?
Connection loss?
Consumer crash?

Step 6: Decide repair

ignore stale duplicate
replay missing message
manually repair aggregate state
rebuild projection
quarantine affected aggregate
patch consumer transition guard

29. Design patterns for ordering

29.1 Strict low-volume order

single queue
single active consumer
prefetch 1
sequential consumer
state transition guard

Use for:

critical state machine commands
low-volume control flow
approval transition executor

29.2 Per-aggregate ordered parallelism

route by aggregate hash
N shard queues
single active consumer per shard
aggregate version check

Use for:

order lifecycle
quote lifecycle
customer account updates

29.3 Unordered high-throughput tasks

work queue
many consumers
prefetch tuned
idempotency key
no strict order assumption

Use for:

email notification
report generation
cache refresh
non-critical integration job

29.4 Projection with gap detection

event contains aggregateVersion
consumer tracks lastAppliedVersion
if version == last + 1: apply
if version <= last: discard duplicate/stale
if version > last + 1: park and wait/reconcile

Use for:

read model
search index projection
audit projection

30. Anti-patterns

30.1 Assuming FIFO with multiple consumers

Queue is FIFO, so five consumers are still ordered.

Wrong at commit level.

30.2 Blind requeue

nack(requeue=true) for every exception

Creates redelivery loop and ordering inversion.

30.3 Retry queue for strict sequence

Delayed retry lets later messages pass earlier failed messages.

30.4 Manual replay without state guard

Old messages can apply after newer state.

30.5 Using priority for lifecycle events

Priority intentionally violates FIFO.

30.6 Global ordering requirement without business justification

Global ordering kills throughput and usually hides a need for aggregate-level consistency.


31. Example: Order lifecycle command executor

A safer model:

flowchart TD API[JAX-RS API] --> DB[(PostgreSQL command table + outbox)] DB --> PUB[Outbox Publisher] PUB --> X[order.command.exchange] X --> Q0[order.command.shard.00.q SAC] X --> Q1[order.command.shard.01.q SAC] X --> Q2[order.command.shard.02.q SAC] Q0 --> C0[Command Consumer] Q1 --> C1[Command Consumer] Q2 --> C2[Command Consumer] C0 --> S[(Order state table)] C1 --> S C2 --> S

Message metadata:

{
  "messageId": "msg-...",
  "commandId": "cmd-...",
  "aggregateType": "Order",
  "aggregateId": "order-123",
  "aggregateVersion": 12,
  "commandType": "SubmitOrder",
  "correlationId": "corr-...",
  "causationId": "http-request-..."
}

Consumer rule:

apply only if current state allows transition
apply only if command not processed
commit DB
ack message

32. Example MyBatis guard

public void handle(OrderCommandMessage message) throws IOException {
    try {
        transactionTemplate.executeWithoutResult(tx -> {
            boolean firstTime = inboxMapper.insertIfAbsent(
                message.messageId(),
                "order-command-consumer"
            ) == 1;

            if (!firstTime) {
                return;
            }

            int updated = orderMapper.transition(
                message.orderId(),
                message.expectedVersion(),
                message.expectedState(),
                message.targetState()
            );

            if (updated != 1) {
                throw new OutOfOrderOrStaleMessageException(message.messageId());
            }
        });

        channel.basicAck(message.deliveryTag(), false);
    } catch (OutOfOrderOrStaleMessageException e) {
        // classify: stale duplicate, gap, invalid command, or poison
        channel.basicReject(message.deliveryTag(), false);
    } catch (Exception e) {
        // send to controlled retry topology, not blind requeue
        channel.basicNack(message.deliveryTag(), false, false);
    }
}

Key point:

Ordering correctness is enforced at DB state transition, not assumed from broker delivery.

33. Internal verification checklist

Verify in CSG/team context:

Which flows require ordering?
Is ordering global, per tenant, per quote, per order, per customer, or per workflow?
Which queues have multiple consumers?
What is prefetch per consumer?
Are consumers internally parallel?
Are Kubernetes replicas allowed to scale freely?
Is single active consumer used anywhere?
Are queues sharded by aggregate?
How is aggregate key represented in routing key/header/payload?
Does message contain aggregate version?
Does DB enforce state transition order?
Does outbox publish per aggregate in deterministic order?
Can retry topology reorder messages?
Can DLQ replay introduce old messages into live processing?
Are priority queues used for lifecycle messages?
What metrics detect stale/out-of-order events?
Are ordering incidents documented?
Who owns repair when ordering fails?

34. PR review checklist

Ask these questions before approving RabbitMQ-related PRs.

34.1 Ordering requirement

Does this flow require ordering?
What is the ordering scope?
What happens if messages arrive out-of-order?

34.2 Topology

How many queues?
How many consumers?
Is single active consumer needed?
Is sharding needed?
Is routing key deterministic?

34.3 Consumer behavior

Is processing sequential or parallel?
What is prefetch?
When does ack happen?
What happens on failure?
Is blind requeue avoided?

34.4 Retry/DLQ

Can retry reorder messages?
Can DLQ replay violate state?
Is replay guarded by idempotency and version checks?

34.5 Database correctness

Is there aggregate version?
Is there optimistic lock?
Is invalid transition rejected?
Are duplicates detected?

34.6 Observability

Can we see aggregateId and aggregateVersion in logs?
Can we detect stale/gap/duplicate events?
Can we reconstruct timeline after incident?

35. Decision matrix

RequirementRecommended modelAvoid
Strict global orderSingle queue + one consumerHigh consumer concurrency
Per-order orderSharded queues by orderId + SACRandom routing
High-throughput independent taskWork queue + many consumersArtificial global order
Event projectionVersioned event + gap detectionBlind apply by arrival order
Retry-sensitive ordered flowPark aggregate or controlled retryDelayed retry without state guard
Urgent operational jobPriority queue if order not importantPriority for lifecycle transitions
Replayable ordered streamRabbitMQ Stream partitioned by keyQueue treated as long-term log

36. Production readiness summary

A RabbitMQ ordering design is production-ready only if it answers:

What is the ordering scope?
Where is order enforced?
Where can order break?
How do we detect out-of-order processing?
How do we repair affected aggregates?
How do retries interact with order?
How does Kubernetes scaling interact with order?
How does DB state protect correctness?

Senior rule:

Do not rely on broker FIFO to protect business state.
Use broker ordering as one layer, then enforce domain correctness in consumer and database.

37. References for further internal study

Use these as external reference anchors, then verify against the RabbitMQ version and deployment used internally:

RabbitMQ Queues documentation
RabbitMQ Consumer Acknowledgements and Publisher Confirms
RabbitMQ Consumer Prefetch documentation
RabbitMQ Single Active Consumer documentation
RabbitMQ Priority Queues documentation
RabbitMQ Quorum Queues documentation
RabbitMQ Streams documentation
RabbitMQ Reliability Guide
Lesson Recap

You just completed lesson 15 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.