Repository navigation
[Mocha] Fix Postgres outbox reordering messages from one transaction - #10699
Merged
PascalSenn merged 2 commits intoOct 28, 2026
Merged
Conversation
Outbox rows written in one transaction all get created_at = NOW(), the transaction start, and the claim ordered by created_at alone. Through a backward scan of the descending created_at index, tied rows were claimed newest first, so messages from one transaction were sent in reverse order. Generate outbox ids that strictly increase within the process (UUIDv7 with a counter after the millisecond timestamp) and order the claim by created_at, then id. No schema or migration change.
PascalSenn
reviewed
Oct 26, 2026
PascalSenn
requested changes
Oct 26, 2026
PascalSenn
approved these changes
Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
With the Postgres outbox, messages published inside one database transaction were sent in reverse order. A handler that publishes two events for the same stream in one transaction delivered the second before the first.
The insert stamps
created_atwithNOW(), which is the transaction start, so every row of one transaction gets the same value. The claim picked the next row withORDER BY "created_at" ... LIMIT 1and no tie-breaker. The planner serves that through a backward scan of the descendingcreated_atindex, which returns tied rows newest first. The row id could not break the tie:Guid.CreateVersion7()is random within a millisecond, and the net8 path usedGuid.NewGuid().This change:
OutboxMessageIdGenerator. It produces UUIDv7 values that strictly increase in uuid byte order within the process, using a counter after the millisecond timestamp, and is the same on every target framework.created_at, thenid.There is no schema, model or migration change. The existing
created_atindex still drives the claim: Postgres uses an incremental sort over it and only sorts the rows that share the oldestcreated_at.New tests:
Outbox_Should_DeliverMessagesInPublishOrder_When_PublishedInOneTransactionpublishes 50 messages in one transaction and checks the delivery order. Before the fix it failed on every run, with the messages arriving from 49 down to 0.Outbox_Should_DeliverMessagesInPublishOrder_When_EachPublishedInOwnTransactionis a regression guard for one message per transaction.OutboxMessageIdGeneratorTestschecks that ids generated within the same millisecond strictly increase, and that version and variant bits are set.