昨天處理 Partial Failure 的時候,我已經開始讓 Workflow 知道某一步失敗後,可以選擇 Skip、Fallback 或留下狀態繼續往下,而不是任何一個 API 出錯就讓整條流程直接停掉。
但實際跑幾次後又出現一個很浪費的狀況,假設前面九個步驟都成功,第十個步驟失敗,我按下重新執行之後,前面九個步驟居然又全部跑了一次。
假設今天有一條整理每日資訊的 Workflow:
抓 Gmail
整理信件
抓 Calendar
讀取待辦
AI 摘要
分類
產生今日清單
寫入資料庫
產生報告
發送通知
前九步都已經完成,最後發送通知時 API 剛好 Timeout,整個 Run 被標記成 Failed。
如果最簡單的 Retry 是:
重新跑整個 Workflow
那 Gmail 又抓一次、Calendar 又讀一次、AI 又重新呼叫一次、資料庫可能還再寫一次。
除了浪費 API Call 和 Token,有些 Action 甚至會造成更麻煩的結果,例如重複寄信、重複新增資料、同一筆任務建立兩次。
所以今天我想處理的是 Retry 的粒度。
之前我的 Workflow 比較像一條線,只知道整次執行最後是 Success 還是 Failed。
現在我開始把每一個 Step 都留下狀態:
Run #1042
Step 1 Gmail Fetch SUCCESS
Step 2 Mail Parse SUCCESS
Step 3 Calendar Fetch SUCCESS
Step 4 Task Fetch SUCCESS
Step 5 AI Summary SUCCESS
Step 6 Classification SUCCESS
Step 7 Build Todo SUCCESS
Step 8 Database Write SUCCESS
Step 9 Report Generate SUCCESS
Step 10 Notification FAILED
這時候 Retry 就不用回到 Step 1,可以直接從 Step 10 繼續。
Retry
→ 找到 FAILED Step
→ 讀取前一步 Output
→ 重新執行 Step 10
前面已經成功的結果全部保留。
但只記 SUCCESS 還不夠,因為 Step 10 可能需要 Step 9 的結果。
假設 Step 9 產生:
{
"report_id": "R20260921",
"summary": "...",
"task_count": 8
}
如果 Workflow 重啟後這些資料已經消失,那即使知道 Step 9 成功也沒有辦法直接往下接。
所以每一步除了 Status,我還開始保存 Input 和 Output:
step_id
status
input
output
started_at
finished_at
error
這樣重新執行時就可以從最後一個成功的 Checkpoint 接回去。
做到這裡我第一個想到的其實很像遊戲存檔。
如果玩了一小時後角色死掉,我通常不會希望整個遊戲從開頭重新開始,而是回到最近一次存檔;Workflow 也是一樣,前面已經完成而且結果可以重複使用,就應該留下一個 Checkpoint。
例如:
Checkpoint A
抓資料完成
Checkpoint B
AI 處理完成
Checkpoint C
資料寫入完成
Checkpoint D
通知完成
如果最後通知失敗,可以從 Checkpoint C 繼續。
這樣流程越長,差異會越明顯,尤其裡面如果包含 LLM、圖片處理、外部 API 或需要幾分鐘才能完成的任務,重新跑前面所有步驟的成本其實非常高。
這裡還有另一個問題。
例如:
Step 8:寄送付款通知
第一次 API Timeout,不一定代表通知沒有寄出去,也可能是對方已經收到,只是我的系統沒有拿到成功 Response。
這時候直接 Retry:
Send Email
Send Email
使用者就可能收到兩封一模一樣的信。
如果動作是:
建立訂單
扣款
新增 Calendar Event
發送訊息
重複執行的風險會更大。
所以 Retry 前還要知道這個 Step 能不能安全地重複執行。
我開始替這類 Action 加一個唯一的 Key,例如:
notification_20260921_user123
每次執行前先檢查:
這個 Key 做過了嗎?
如果已經成功執行,就直接使用原本結果;如果沒有,才真的呼叫外部服務。
這樣就算 Workflow 因為網路問題 Retry,也可以降低重複操作的機率。
最後流程會比較像:
Step Failed
→ 檢查是否可 Retry
→ 檢查 Idempotency Key
→ 找最近 Checkpoint
→ 只執行失敗 Step
→ 成功後繼續後面流程
前幾天我還覺得 Retry 就只是「失敗再跑一次」,但一路做到今天後才發現,真的要讓 Workflow 自己恢復,需要知道哪一步失敗、前面做到哪裡、哪些結果可以沿用,以及某個 Action 重跑之後會不會產生副作用。
當這些狀態都留下來後,我才比較敢把 Retry 自動化,因為它不會一出問題就把整條流程從頭再跑,也比較不容易因為一次 Timeout 製造出更多重複資料。
明天我想再往前一步,既然 Workflow 已經知道哪些 Step 成功、哪些 Step 失敗,那系統其實可以開始自己判斷錯誤類型,有些錯誤等一下重試就好,有些錯誤重試十次也不會成功,這兩種情況應該要有不同的處理方式。