await db.save(order); await bus.publish(event);
Process chết giữa hai dòng → DB có đơn, không event — hoặc ngược lại
tùy thứ tự. Hai hệ =
không atomic.
Outbox (và inbox phía nhận) là đường chuẩn. Dual-write “cho nhanh” để lại ticket “đơn có / mail không”.
Dual-write fail modes
DB ok, publish fail → missing event (view/job đói)
Publish ok, DB fail → ghost event (consumer làm việc trên state không có)
Retry publish → double event nếu consumer không idempotent
Nối missing / wrong, exactly-once thực dụng.
Timeline fail
t0 BEGIN; insert order; — crash trước commit → không có gì (OK)
t1 COMMIT order
t2 publish event — crash trước t2 → DB có, bus không (missing)
t2' publish ok; crash trước consumer xong → redelivery (cần idempotent)
t3 publish trước; DB fail — ghost event (consumer “làm” state ảo)
Outbox ý tưởng
BEGIN
insert business row
insert outbox(event)
COMMIT
— publisher poll outbox / CDC — at-least-once publish
— consumer idempotent
Vì sao vẫn dual-write
Nhanh, demo, “tạm”. Chi phí: support + job backfill + mất niềm tin event-driven. Mỗi tuần “tạm” là một tuần ticket missing event.
Một câu
Hai hệ = không atomic — outbox (hoặc tương đương), đừng tin hai dòng code.