自動化腳本把自己重試死第三次之後,我們把觀察和執行拆開了

上個月二點檔的發布任務,同一個 bug 報了三次。第一次,腳本自建自清理;第二次,它把異常吞掉變成空結果;第三次,它自己探測自己寫的檔案,探測結果反過來又變成重試條件,空轉了四十分鐘。直到我們把「判斷該不該重試」從執行行程裡搬出去,這種活鎖才停下來。記錄一下。

專屬插圖
自動化腳本把自己重試死第三次之後,我們把觀察和執行拆開了

自動化腳本把自己重試死第三次之後,我們把觀察和執行拆開了

上個月二點檔的發布任務,同一個 bug 報了三次。第一次,腳本自建自清理;第二次,它把異常吞掉變成空結果;第三次,它自己探測自己寫的檔案,探測結果反過來又變成重試條件,空轉了四十分鐘。直到我們把「判斷該不該重試」從執行行程裡搬出去,這種活鎖才停下來。記錄一下。

三次死法

第一次很乾脆:執行腳本在 V4 入庫這一步全量失敗,schema 衝突,catch 住異常、清理暫存目錄、exit 0,日誌一行紅字。當時失敗了,但除錯路徑是通的。

第二次更隱蔽:同一段程式碼,這次異常在翻譯轉換層被包裝後回傳成了空物件,執行方把空物件當成正常結果,既沒有 diff 也沒有報錯。任務顯示「完成」,報告裡正常記錄了三語審核。這次是假成功,比失敗難發現得多。

第三次才打出房間:腳本每輪輪詢都會重建暫存目錄,並且把上一輪的失敗輸出寫回收查檔案;複查邏輯發現複查檔案存在就再次觸發清理,清理又回填複查檔案。執行行程頭部的判斷和尾部的動作咬住了自己,空轉四十分鐘,每輪報告都寫同一句「清理完成」。

除錯時卡住的地方

最卡住的不是定位,是沉默:任務顯示完成、報告留了記錄、監控沒有任何異常。真正的訊號藏在報告外面——複查檔案的修改時間比觸發時間還早。同一條規則在檔案裡出現了兩次,一次是判斷條件,一次是動作產物。

更尷尬的是,當時寫「一鍵清理」這個功能的需求就是我提的,理由很簡單:一條指令把上一輪殘留清乾淨。需求本身沒毛病,但觀察者和執行者住在同一個行程裡,觀察到的永遠是執行結果的回聲——這是一個只反映自身狀態的感測器。這個結構坑我們不是第一次踩,最後一次結算是用了整整一個黃金時段。

護欄設計

拆完之後才好談修復。拆開是一次性的,結構才能慢慢改:

**觀察和觸發拆開。** 暫存檔案由寫入方在原子寫時更新完成標記;重試方只讀完成標記做判斷,只讀就永遠不會重新觸發動作。

**每份暫存產物帶 owner 和 TTL。** 不同來源的檔案不再互相覆蓋;TTL 內 owner 沒重新整理,不管它是不是還活著,下個階段一律跳過,交給獨立的過期回收。

**空結果與錯誤分道。** 轉換層不許把異常包裝成空物件;空結果必須走獨立的 diff 分支,報告裡要麼有正文,要麼有一段 diff 說明為什麼是空。

**報告加一欄「變更數」。** 執行了但什麼都沒變的輪次,這欄是零。零不一定錯,但它應該被人看了一眼,而不是被「任務完成」三個字蓋過去。

風險面暫時用三條路由層的覆蓋率兜底(這三條判斷後來也全量拆到了獨立行程)。改完之後連跑三天,同類空轉只有一次苗頭,且被「變更數為零」的告警攔在了讀者看見之前。當天真正需要變更的那一輪,diff 裡能指出具體的 schema 欄位,不是空轉。

一句話

如果你的執行腳本能自己驗證自己的產出,它就能給自己簽發通行證。驗證者必須獨立於執行者,觀察也必須獨立於執行——誰都不許住在自己造出來的訊號裡。

留言區

歡迎分享你的想法!

發表留言

0/500

載入留言中…