概念地圖・交易式 Outbox
分類:系統
這一頁不只是把一個概念解釋一遍——一張自己畫的結構圖,加上拿台灣情境從頭走一次的實例演練、這個框架什麼時候會騙你,以及它接回 P.A.I.N. 之後長什麼樣。一頁必須交付的三層 →
交易式 Outbox:資料與通知一起成立
清運車回場,司機把作業紀錄寫進系統,同一個動作也在待申報清單上留了一行。要等那一行真的送進主管機關的申報系統,這一趟才算結束。麻煩的是,資料庫寫入與訊息送出分屬兩套東西,中間沒有人保證它們同進退——帳上這趟活做完了,該讓別人知道的那一半卻卡在半路。交易式 Outbox 的做法很土:把「要通知誰」也寫成一張表的一行,跟業務資料塞進同一筆交易,再派一支轉送器把那些行讀出來送走。
同一筆交易裡,兩張表各放什麼
這張圖真正的機關,全在左邊那個框裡的分工。兩張表寫進同一筆交易,性質卻完全不同。
| 問題 | 業務資料表 | 待送事件表 |
|---|---|---|
| 放什麼 | 這趟清運真的發生了什麼 | 該讓誰知道的那一則訊息 |
| 誰在讀 | 你自己的系統、報表、對帳 | 只有轉送器,讀完就不再回來 |
| 什麼時候寫 | 司機回場按下送出 | 同一筆交易、同一個瞬間 |
| 交易失敗了 | 整趟紀錄回滾,等於沒寫 | 一起回滾,通知不會憑空冒出來 |
| 事後會變嗎 | 幾乎不再動 | 狀態從待送翻成已送,翻之前重啟就會重送 |
| 會長大嗎 | 照業務量長,本來就該長 | 沒有清理機制,它會變成第二張要維護的表 |
讀完這張表會發現,交易式 Outbox 沒有變出任何新的保證——它只是把「訊息送不送得出去」這個賭注,從網路那一端搬回到資料庫這一端。資料庫本來就會替你守住一筆交易的全有全無,那就把要通知的事,也塞進那個守得住的範圍裡。至於出了框之後的事,圖右半邊那三個箭頭都得靠工程自己扛。
你這條流程需不需要這一層
多養一張表、多顧一支背景程式,值不值得,要看流程長什麼樣。底下四個自問,都是站在截油槽清運那個場景裡問的。
一、回場之後,還有沒有第二個動作。清運車進場、過磅、噸數寫進系統,這趟活在你自己的帳上已經結案。但主管機關那一端還沒收到任何東西——這趟活的「完成」,有一半握在別人手上。自問:這件事做完的定義,會不會有一半在我的資料庫外面?
二、那個收件人,進不進得了同一筆交易。申報系統是別人的機器,你沒辦法把它拉進你的交易裡。但你可以把「要對它說的話」寫成自己資料庫的一行——這一步,就是整個做法的全部發明。自問:我能不能把要通知的內容,先變成一筆自己寫得起的資料?
三、漏掉那一趟,你多久會發現。一天 42 趟回場、一個月 1260 趟,其中 3 趟寫成功卻沒送出,比例只有 0.24%。看起來像雜訊,但那 3 趟很少在當天現形——多半要等稽查通知寄到,中間隔了幾個月。自問:今天有一趟沒申報成功,我是幾分鐘後知道,還是等別人來告訴我?
四、下游認不認得同一張聯單。轉送器保證的是「至少送到一次」,順帶保證了「有時候會送兩次」。如果申報端把同一個聯單編號收兩遍就長出兩筆紀錄,你等於用一種錯換掉另一種錯。自問:同一個編號連送三次進去,對方會產生三筆,還是一筆?
一趟清運的回場流程,兩張表各填一次
底下這家台灣中部的截油槽清運業者,是為了把這個結構講清楚而編出來的示意情境,沒有指涉任何真實公司。
- 下午四點十分,回場過磅:司機在車機上按送出,車號、清運起訖時間、噸數寫進業務資料表。這一步從來沒出過問題,它就在自己的資料庫裡
- 同一筆交易,多寫一行:待送事件表多出一行,帶著清運聯單編號、廢棄物代碼與這一車的噸數,狀態欄寫「待送」。兩張表要嘛一起進去,要嘛一起不進去
- 轉送器每 5 秒撈一次:背景程式把狀態是待送的那些行讀出來,一行一行推進申報系統。車機當機、司機關掉 App,都影響不到這一步
- 送出去之後,回頭改狀態:申報系統回應成功,轉送器把那一行標成已送。標記之前服務被重啟,這一行下一輪還會再送一次
- 下游用聯單編號比對:申報端收到同一個編號的第二次,直接丟掉。重送從災難降級成噪音,就是靠這一步
- 三十天後清掉已送的行:排一支例行工作,把標成已送滿三十天的行搬去封存。沒人排這件事,半年後它會比業務資料表還大
整條流程真正值得記住的接縫,在第二步跟第四步中間那段空白。第二步結束的那一瞬間,這趟清運已經「一定會被申報」——因為那一行躺在自己的資料庫裡,重啟、斷網、機房跳電都拿它沒辦法。但第四步之前,它同時也「一定可能被申報兩次」。這家業者上線後量了一個月:漏送從 3 趟變成 0,重送則出現 9 趟,1260 趟裡佔 0.71%。九趟重複的申報,靠下游比對聯單編號全數擋下,沒有一筆變成重複申報紀錄。他們換掉的東西很明確——把「有幾趟永遠不會被發現」的風險,換成「有幾趟會吵一下、但看得見」的風險。以上為說明結構用的示意情境,僅供理解框架之用。
這一層裝上去之後,還有三種漏法
- 同一件事會被送兩次,這是設計上的正常結果。轉送器推完訊息、還沒把那一行標成已送就重啟,下一輪自然會再撈到它。實測一個月約 9 趟被重送,1260 趟裡的 0.71%——比原本 0.24% 的漏送還高。差別在於重送吵得出聲音、漏送安安靜靜。所以下游一定要能用清運聯單編號認出重複;下游做不到,這一層就只是把問題換了個地方擺。
- 送出去這件事,永遠慢一拍。最壞情況是掃描間隔的 5 秒,加上申報系統自己的回應時間。平常沒差,但只要有人在畫面上按了送出、然後盯著看「對方收到了沒」,這幾秒就會變成客訴。把掃描調到 1 秒也治不好——那只是把等待從 5 秒縮到 1 秒,卻讓資料庫多吃五倍的查詢。真正該做的是把介面上那句話改成「已排入申報」。
- 待送清單自己會長大。沒有清理機制的話,它會變成第二個要維護的資料表:索引要顧、備份要算它的體積、狀態欄查詢會越來越慢。一天 42 行看不出來,一年一萬五千行也還好,三年之後那張表比業務資料表更常出現在慢查詢清單上。上線那天就要一併排好封存的例行工作,別等它先痛一次。
那份沒送出去的申報,最後誰在賠
這張圖上最貴的一格,其實在畫布外面。
技術上的損失小得可憐——1260 趟裡漏 3 趟,任何工程師都能說服自己那是可接受的誤差。但把那 3 趟攤在客戶面前,長相完全不同:稽查通知寄到某家餐廳,餐廳翻出清運單據,發現清運公司三個月前那一車沒有對應的申報紀錄。餐廳老闆不會去理解什麼叫交易邊界,他理解的是「我付了錢,你沒做完」。這張圖畫得出訊息有沒有離開你的機房,畫不出那位老闆掛掉電話之後,把你歸到哪一類的清運廠商。P.A.I.N. 的第一個字是「誰在痛」——這條流程裡痛的人從來不是你的資料庫,是那個要拿紀錄去應付稽查的人。今晚花十分鐘,翻出上個月被退件或補件的每一件,逐件標一個原因代號:是資料填錯,還是根本沒送到。這兩堆的比例,會直接告訴你該不該多養一張表。
關於交易式 Outbox,常見問題
跟直接在程式裡先寫資料庫、再呼叫申報 API,差在哪裡?
差在中間那個縫。先寫資料庫、再呼叫 API,兩個動作之間有一段沒有人守著的空檔——服務在這裡重啟,或網路在這裡斷掉,資料就進去了、訊息卻永遠不會送。交易式 Outbox 把訊息也變成一次資料庫寫入,塞進同一筆交易,於是那段空檔被搬到「已經確定要送」之後。轉送器就算掛掉,那一行仍然躺在待送清單裡等著;它復活之後接著撈就好。
一定要另外開一張表嗎?直接讀資料庫的變更紀錄不行嗎?
讀變更紀錄(CDC)當然可行,而且省掉一張表。代價是你送出去的訊息長得跟資料表的欄位一模一樣——欄位改名、加一欄、拆一張表,下游全部要跟著改。另外開一張待送事件表,等於自己決定「要對外說什麼」,內部結構怎麼翻都不會波及外面。小系統用變更紀錄比較快;跨部門、跨公司要對接的訊息,多養那張表通常換得回來。
轉送器多久掃一次比較合理?
先問「晚幾秒會有人抱怨嗎」。像清運回場申報這種當天結算的事,5 秒綽綽有餘,甚至 30 秒都沒人察覺。掃描間隔縮短一半,資料庫的查詢量就翻倍,而換來的只是使用者感覺不到的幾秒。比較划算的做法是保留固定間隔當保險,再讓寫入端在交易提交後順手戳轉送器一下——平常靠戳的走得快,戳失敗了還有定時掃描兜底。
下游沒辦法去重,怎麼辦?
那就把去重搬到自己這一側,效果打折但堪用:在轉送器與下游之間放一張已送編號表,送出前先查一次。這只能擋掉大部分,因為「查完到寫入」之間仍然有縫。真正乾淨的解法還是請下游把聯單編號當成唯一鍵。談不動的時候,退一步的做法是每天跑一次對帳,把自己送出的編號跟對方回傳的清單比一遍,重複的手動註銷——麻煩,但至少看得見。
圈出你那條流程裡「先做完、再通知」的地方,就知道哪幾個縫需要這張待送清單。往下走還有兩條相關的路:下游來不及消化時要怎麼往上游喊停,可以看背壓與流量控制;一份單據沿著一站一站被押著走、每站都要留下憑證的結構,則在信用狀流程那張圖上畫得更完整。
喜歡這篇文章嗎?
每週幫你拆二本好書,把判斷力、好書金句、好問題,直送你的信箱。
不適合你可隨時退訂 · 每週兩封,陪你把書讀進生活。
送出即表示同意我們依隱私權政策使用你的 email。