Message flow
One committed row becomes one Kafka message. Below, three aggregates make that trip at once — each keeps its own order, at its own pace, and a failure on one never touches the others.
What the picture is hiding
BEGIN TX UPDATE aggregate SET version = version + 1 WHERE id = ? INSERT INTO tandem_outbox (aggregate_id, bucket, seq, payload) COMMIT TX
The domain change and the outbox row are one commit. Nothing publishes yet (stage 1 is just a row becoming durable).
bucket = hash(aggregate_id) mod B, computed once at insert. Every event of
an aggregate lands in the same bucket, and each bucket is owned by exactly one worker
(ordering is structural, not a lock that a race can lose).
A worker claims its bucket's PENDING rows in id order with
FOR UPDATE SKIP LOCKED and marks them IN_FLIGHT under a lease.
If the worker dies mid-publish, the lease expires and another worker reclaims the row.
The row is wrapped in a CloudEvents envelope and published with
key = aggregate_id. Only a Kafka ack marks the row DONE (a
crash between the two produces a duplicate, never a gap).
After its retries are exhausted, a row is marked FAILED and the relay stops
advancing that aggregate (its later events queue behind it). Every other
aggregate, including ones sharing its bucket, keeps flowing: SKIP LOCKED
steps around the stuck row instead of stalling on it.