假設訂單 O42 在 09:57 付款成功,金流通知也已送達。10:00 資料庫故障,工程師把 MySQL 還原到 09:55。訂單又變成「未付款」,金流端那筆錢卻還在。
這時如果重新打開結帳、啟動付款與出貨 worker,程式會依照五分鐘前的資料繼續工作。原本正確的「查不到紀錄就建立」可能再做一次已完成的操作。恢復方案因此要有一個明確決定:還原完成後先維持停寫,核對受影響的訂單、付款與庫存,通過放行條件才重新接單。
商店、事故時間與下方數字都是教學假設,沒有執行 MySQL 還原或金流演練。這一課沿用模組化單體,討論何時恢復業務,不教雲端操作指令。先前的 SQLite 還原演練已量測本機恢復時間;這次補上資料庫以外仍然存在的付款結果。
還原點之後發生的事,要去哪裡找?
O42 在 09:55 已經存在,當時還沒付款。09:57 金流完成收款,09:58 應用保存成功通知並回了 2xx。還原之後,訂單、付款紀錄與 processed_events 都回到較早狀態,三張表彼此可能完全一致,卻一起漏掉同一筆付款。
下單冪等收據保護的是仍然保存在權威資料庫裡的操作結果。資料庫倒退後,唯一鍵擋不住一筆已經消失的收據。外部金流的冪等鍵也有保留期限,例如 Stripe API v1 文件允許在鍵至少存在 24 小時後清除,清除後再次使用會成為新請求。恢復時應查原付款物件的結果,不能假設拿原鍵重送永遠安全。Stripe 冪等請求
O42 可以用既有 PaymentIntent ID 查回狀態,核對所屬帳戶、商戶訂單關聯、金額與幣別,再補回本地已確認的收款事實。這個查詢依據來自金流的 Retrieve PaymentIntent API,補資料的交易與審核規則則是商店自己的設計。
另一種情況更麻煩。若 O43 連訂單本身都在 09:55 之後才成立,還原庫裡根本沒有它。這時要從供應商可列出的付款資料、獨立保存的操作紀錄,以及仍可安全讀取的故障庫尋找對應。只有付款金額,無法推回顧客買了哪些商品、當時的報價和配送快照。證據不足就保留待核對,禁止再扣款或派工,不用目前購物車補造歷史訂單。
因此恢復清單不能只掃還原庫中「未付款」的訂單。它還要包含還原點之後可能消失的操作、故障前已開始但稍後才完成的付款,以及恢復期間收到的新通知。入口紀錄若也只存在同一份被還原的資料庫,這份清單就可能不完整。
先停全店寫入,讓核對範圍不再持續擴大
首版選擇暫停接單、付款提交、取消、退款與出貨派工,保留維護頁及受控查詢。舊主庫與舊 worker 必須確認不能繼續寫入或呼叫金流,才建立恢復用的新環境。只改 DNS 或應用連線字串,仍可能留下持有舊連線的程序與排程。
以 RDS for MySQL 為例,PITR 文件說明還原會建立新 instance,原 instance 不會被修改。這很適合隔離驗證,也表示舊環境不會因為新庫建立就自動消失。恢復清單要記錄來源、目標、還原時間與實際設定,不能只寫「DB 已起來」。
還原庫回到 09:55,09:57 的付款仍存在。(教學假設)
1 · 隔離舊環境
暫停 API、worker、排程與外呼。可靠保存新通知,核對仍在途的付款。
2 · 還原並核對
新庫仍停寫。核對付款、原訂單、冪等結果與預留。包含消失的訂單和已收過的通知。
3 · 驗證後接單
只保留一個寫入環境。驗證原鍵重試、重複通知及第一筆新單,確認不重複扣款與預留。
金流仍可能完成已在途的交易。維護期間若有獨立、可靠的收件區,先保存通知再回 2xx,由恢復流程核對;若無法可靠保存,就回失敗並依供應商機制重試,另行補查。Stripe 不保證事件順序,也可能重複投遞,所以補查與新通知可能碰到同一筆付款,仍要以供應商物件/事件識別值去重。Webhook 投遞行為
09:58 已成功回覆的通知尤其容易漏掉。它不會因為商店日後還原資料庫就自動變成投遞失敗。若使用 Stripe Events API補查,應依受影響時間範圍分頁取得事件,連已成功投遞者一起核對。可用事件僅涵蓋最近 30 天,不能把這個介面當作永久業務備份。
讀到付款成功也不立即出貨。先恢復訂單原有條件與庫存預留,收款和履約資格分開判斷。若訂單在事故前已取消,則回到晚到付款的處理規則,不能為了讓資料看起來一致就把取消改回成立。
RTO 要把對帳時間算進去
假設業務提出 RPO 5 分鐘、RTO 60 分鐘。RPO 是可接受的資料恢復點缺口,RTO 是從中斷到恢復服務的可接受時間;它們要由業務需求決定。AWS 復原目標
先用五分鐘評估工作量。假設該時段每分鐘有 20 筆付款,5 分鐘 × 20 筆/分鐘 = 100 筆需要核對的候選。這不是已證明遺失 100 筆,也不包含停機後完成的在途付款。若故障是誤刪資料而必須回到更早時間,候選區間還會變長。
再分配 60 分鐘的恢復預算。假設偵測及隔離 5 分鐘、建新庫與還原 25 分鐘,剩下 30 分鐘必須容納核對、修復、切換與讀寫驗證。若其中 15 分鐘拿來人工核對,每筆平均 30 秒,只能處理 15 × 60 ÷ 30 = 30 筆。100 筆候選全靠人工就超出預算,需要自動核對可明確匹配的個案,把人工留給差異。
上述時間是規劃輸入,還沒證明做得到。RDS 的 available 後仍可能在背景載入資料塊,效能尚未完全恢復;恢復庫需要跑代表性讀寫。RPO 也不能替已回覆成功的訂單取消承諾。即使可接受暫時五分鐘缺口,消失的已確認訂單仍須找回或正式處理,另列筆數與金額,不用「符合 RPO」結案。
三個問題答得出來,才開放第一筆新單
負責恢復的人要能指出目前唯一能接收寫入的環境,並留下舊 API、worker、排程與外呼路徑已隔離的證據。接著逐筆核對受影響清單,確認付款結果、原訂單快照、冪等結果與庫存預留已恢復一致。最後用受控的新操作驗證正常路徑,讓讀取和寫入都經過預定的正式入口。
可以把這段驗收放進隔離環境的演練,安排幾筆有明確預期的合成資料:
- O42 已收款且通知已成功送達,還原後變回未付款。核對後只能留一份收款結果,外部扣款次數不增加。
- O43 在還原點之後成立。即使還原庫查不到,也必須出現在候選清單,缺少原始明細時不得自動重建或出貨。
- O44 的金流查詢逾時。它維持未決,不因為新的 worker 開始執行就換鍵重扣。
- 舊 worker 在新庫驗證期間恢復連線。它必須仍被隔離,不能產生額外退款或覆寫恢復結果。
對每筆資料,同時比對外部付款物件、本地訂單與預留,以及恢復前後的操作次數。再測原鍵重試、重複通知與恢復後第一筆新單。單查資料表筆數相同,抓不到「少一筆又多一筆」的錯誤。AWS 復原演練指引也要求驗證實際恢復時間、指定狀態與恢復端容量。
這個最小方案的停止規則很保守:受影響範圍仍無法列清、共享庫存仍有未解差異,或外部副作用仍未決,就維持全店停寫。 代價是較長的中斷,但恢復人員不必同時證明新舊兩批操作互不影響。
何時值得改成部分恢復?
另一個有意義的選擇是把受影響訂單隔離,先恢復其他流量。它能縮短部分顧客的等待時間,但只在訂單上加 recovery_pending 不夠。新單可能賣掉舊單已預留的同一件商品,客戶端也可能用舊鍵重試,背景退款與出貨更不能越過隔離條件。
若演練顯示全店停寫持續超過 RTO,才值得增加這套分流能力。要先證明哪些庫存、操作鍵與外部工作完全不受影響,再對該範圍開放。若大多數時間耗在建立新庫,則優先評估預備環境;若耗在找不到操作證據,就補上獨立保存、可核對的紀錄。增加副本或改成微服務都不會自動補回遺失的付款關聯。
下一次演練,把結束點放在「第一筆符合訂單契約的新操作成功」,並另外留下未決清單與完整恢復時間。這樣量到的時間才包含重新接單所付出的工作。
練習
O42 已確認付款成功,原訂單與預留都找回了。O43 的付款仍查不到結果,而且兩筆訂單買的是同一個 SKU。維運希望先開放這個 SKU 的新單。你會要求哪些證據才放行?如果只能確認資料庫已還原,你會選擇哪個方案?
參考思路
先確認 O43 是否可能占用同一份庫存、是否有仍在途的付款或出貨,以及原操作鍵會如何處理。查不到付款結果,不能當作未付款或未成立。若無法證明 O43 的影響已被完整隔離,維持該 SKU 停售;採首版全店停寫方案時就繼續停寫。要改成只停這個 SKU,還得驗證跨品項訂單、取消釋放與 worker 都遵守隔離範圍,再接受額外設計成本。
