搜尋文件

輸入關鍵字,搜尋標題與正文。

    訊息佇列與事件驅動架構文件 07/12

    資料庫與訊息的原子缺口

    本文提要

    資料庫提交成功,不代表完成事件已發布;broker 接受事件,也不代表資料庫一定提交。當同一項業務變更要寫兩個系統,必須找出它們之間的故障窗口。

    18px · 寬鬆行距

    閱讀設定

    排版預覽 · 示範文字(可捲動)

    檔案與目錄

    Linux 以一棵從根目錄 / 開始的樹狀結構組織檔案。目錄用來整理檔案,也可以包含其他目錄。

    pwd
    ls -la /home
    18px

    16、18、20、22px;隨瀏覽器文字大小縮放。

    欄寬套用於背景文章;此處預覽字級與行距。手機正文自動配合螢幕。

    文件與本文目錄
    本主題文件

    資料庫提交成功,不代表完成事件已發布;broker 接受事件,也不代表資料庫一定提交。當同一項業務變更要寫兩個系統,必須找出它們之間的故障窗口。

    上一篇已說明重試與安全重送。本文以 export-intent-7、job-42、cmd-42 與 evt-90 為例,讓本地狀態與發布責任一起保存,再限制消費去重真正涵蓋的範圍。基本交易原子性可回查交易與隔離。

    查證日期:2026-11-04。Outbox 參考當日 AWS Prescriptive Guidance,唯一約束與交易語意對照 PostgreSQL 18;事件身分沿用 CloudEvents 1.0.2。所有表名、流程與世代規則是自創概念設計,未執行 SQL、relay 或故障測試。

    調換雙寫順序,只會調換風險

    假設完成契約是「檔案已按規則保存,工作結果可查」,worker 還要發布 ExportCompleted。若資料庫與 broker 沒有共同交易,兩種直覺安排都有問題:

    表格・3 欄 3 列
    順序停止的位置可觀察的不一致
    先提交 job-42=succeeded,再發布 evt-90資料庫成功後、發布前使用者查到成功,下游卻永遠不知道
    先發布 evt-90,再提交成功broker 接受後、資料庫 rollback下游收到完成事實,工作卻沒有已提交的成功結果
    發布後再補記「已送出」broker 接受後、補記前恢復程序無法分辨未送出與已送出但未記錄

    第三列還會出現在修復機制內,不能靠捕捉例外、把發布放進 finally 或立即重試消除。資料庫交易也不會因程式在交易區塊裡呼叫 broker,就把 broker 納入交易。AWS 的 Outbox 指南以相同的雙寫故障作為動機。參考 AWS:Transactional outbox pattern,Motivation。

    Outbox:一起保存狀態與待發布責任

    Transactional Outbox(交易式寄件匣)在擁有業務狀態的資料庫裡,保存待發布訊息,讓業務變更與該紀錄共用本地交易。另一個 relay(轉送程序)只讀已提交的紀錄,負責發布與追蹤交接結果。

    本例完成交易包含:將 job-42 從合法的執行中狀態轉成成功、選定結果識別,以及建立內容固定的 Outbox 紀錄 evt-90。交易 rollback 時,成功狀態與待發布事件一起回復;提交時,兩者一起存在。它保證的是「狀態成立時,有可恢復的發布責任」,不是事件已即時送達所有訂閱。參考 AWS:Transactional outbox pattern,Using an outbox table。

    API 受理端也可在同一交易建立 job-42 與命令 cmd-42 的 Outbox 紀錄,補上第一篇「工作已存在、命令未交付」的缺口。命令與事件仍有不同語意,Outbox 只是它們共用的可靠發布方法。

    Relay 需要明確的待送/確認狀態、重試、積壓告警與清理政策。多個 relay 併行時,要以資料庫 claim 等方式協調紀錄所有權;同一工作若需要順序,也要保存並遵守工作版本,不能只靠讀取時間排序。可以使用輪詢,也有依資料庫變更紀錄的 CDC 路徑;選擇須核對產品能力,新增元件並非模式成立的必要條件。

    Relay 的重複是預期情況

    假設 relay 把 evt-90 送到 broker,broker 已接受,但 relay 在保存已送狀態前停止。下次仍會讀到待送紀錄,於是再次發布同一事件。若反過來先記已送再發布,停止就可能永久漏送。

    因此 relay 必須容許重送,consumer 也必須安全處理重複;AWS 指南亦明列 duplicate messages 的考量。重送時保留同一 evt-90,不能每次產生新 ID。Outbox 的事件身分要持久保存,不能只在 relay 記憶體裡暫時產生。

    事件抵達不同訂閱的時刻仍可能不同。Outbox 本身也不自動保序:平行 relay、重試與 broker 路由都會影響次序。需要每工作保序時,應連同前篇的 key、版本與消費執行規則一起設計;單一事件表不能代替整段契約。

    Inbox:去重紀錄要和自己的效果一起提交

    Inbox(收件匣)/已處理訊息表記錄某個 consumer 已完成哪些事件。本例鍵設為 (consumer, source, event_id):搜尋 consumer 與統計 consumer 各有責任,不能由一個全域 evt-90 已處理 阻止另一個下游執行。source + id 的事件身分來自應用契約,與 CloudEvents 1.0.2 的規則對應。參考 CloudEvents 1.0.2:Required Attributes。

    以效果和 Inbox 都在統計服務同一資料庫為例:

    1. 驗證事件契約,開始本地交易。
    2. 嘗試建立有唯一約束的 (statistics, academy.exports, evt-90) 紀錄。
    3. 若為新紀錄,在同一交易更新統計資料;若效果失敗,整筆交易 rollback,不能留下「已處理」。
    4. 交易提交成功後才結束投遞。若提交結果因斷線未知,恢復後依鍵核對,不能憑例外斷言已 rollback。
    5. 再收到同事件時,若確認已有已提交的 Inbox 紀錄,即可略過同一效果;競爭中的未提交紀錄不能被直接當成完成證據。

    唯一約束是在資料庫邊界裁決競爭;「先查不存在,再各自更新」沒有同等保護。PostgreSQL 18 支援組合欄位唯一約束,但衝突如何回復、何時需重開交易,仍要依實際資料庫與驅動實作,不能捕捉錯誤後假定原交易可繼續。參考 PostgreSQL 18:Unique Constraints。

    這個邊界下,Inbox 和業務變更要麼一起提交,要麼一起回復:若先提交 Inbox 再做效果,可能永久漏做;若先提交效果再寫 Inbox,則重送可能重做。去重保留期也需涵蓋允許的重送與歷史補救,刪掉紀錄後,舊事件不會繼續被這個鍵辨認。

    檔案與通知,仍在本地交易之外

    匯出檔案通常不在工作資料庫裡。本例沿用第五篇的世代規則,把「可能重做計算」和「選定正式結果」分開,而不宣稱檔案只建立一次:

    表格・3 欄 4 列
    階段本例做法故障後留下什麼
    取得工作資料庫原子授予處理世代;過期接手須符合恢復政策舊 worker 仍可能執行,但失去選定結果的資格
    產生檔案每次嘗試寫入專用、不可覆寫的候選檔,確認可依契約讀取完成前停止可留下孤兒候選檔
    選定結果同一資料庫交易檢查目前世代、預期版本與 running,再選定檔案、轉成功並寫 evt-90 Outbox條件不符則不改狀態、不建立完成事件
    清理候選檔依保留與引用關係,回收確定未選用且不再執行中的檔案清理須避免刪掉有效結果或仍在寫入的候選檔

    最後的世代檢查必須和完成更新原子執行;在寫檔前查一次資格並不足夠。這是應用設計:它允許多份候選產物,只選定一份正式結果;不把檔案系統加入資料庫交易,也不保證儲存本身永不故障。結果保存期限、權限與清理政策仍須維護。

    通知更不能靠本地 Inbox 就承諾只寄一次。先寄信再記錄,可能寄出後停止而重寄;先記錄再寄,可能漏寄。在通知資料庫先保存通知工作與自己的 Outbox,能保住再次交付責任;真正外部寄送還要依供應者的冪等鍵、結果查詢與保留期契約核對。若對方沒有這些能力,就須明示可能重複或未知,不能用資料庫 rollback 收回已寄出的信。共同原理見從重送請求到冪等副作用。

    用一段歷程核對三個邊界

    export-intent-7 的受理交易先建立 job-42 與 cmd-42 Outbox;relay 交付命令。worker 的合法世代保存候選檔,再由完成交易選定結果並保存 evt-90。relay 發布後、補記前停止,恢復後重送同事件。統計 consumer 第一回在同一交易提交 Inbox 與統計效果,第二回核對紀錄後不再累加;通知端則依自己的外部寄送契約恢復。

    這段概念歷程分別保住受理責任、完成事實與單一 consumer 的本地效果,沒有把 API、檔案、broker 和通知綁成一筆全球交易。Outbox 尚未送完、下游尚未追上時,資料可以暫時不同步;恢復還依賴 relay 有人維護、訊息未過期及資源恢復,不能只加資料表便宣稱必定完成。

    下一篇把這些身分與版本放入事件封套、Schema 與相容演進,檢查多年後的 consumer 是否仍能理解同一個事實。

    相關文件