Categories
程式開發

事故复盤:我們是怎麼弄丟1400萬條日誌記錄的


在這裡,我們就將涉事企業姑且稱為“某公司”。至於發現問題的開發人員,我們也隱去真名,稱其為王二。

在本文中,我們藉此機會聊聊軟件開發中的人為錯誤,以及針對這些錯誤應當採取的預防措施。

從開發到生產

首先某公司部署代碼庫變更的基本流程總結如下:

  1. 開發人員測試變更內容(屬於流程中的常識性步驟,因此不做強調)。

  2. 代碼審查(僅需要一名審查員,用於審查的時間一般也不長)。

  3. 將代碼 push 至預生產環境。測試預生產環境。

  4. 在 push 至生產環境前重新審查變更清單。

  5. 在 push 至生產環境過程中重新審查變更清單。

  6. 在 push 至生產環境之後重新審查變更清單。

飛行員起飛前的檢查清單列表裡有一句話:

測試標準實踐明確指出,飛行員必須妥善記錄變更清單。

毫無疑問,飛行員在起飛之前當然得對飛機進行一番全面檢查,這是毋庸爭論的事實。如果飛行員沒有負起檢查責任,或者機場工作人員坦言為了避免晚點,檢查過程中省略了某些步驟,乘客怎麼敢貿然登機?反正我是不敢。但不少事故記錄顯示,某些飛行員就是沒能盡到機體檢查義務,最終引發了可怕的悲劇。

某公司雖然不是航空企業,犯錯的代價也不是寶貴的性命,但事故仍然嚴重影響到企業的業務與信譽。如果情況再嚴重一些,這家公司可能將徹底不復存在。

總而言之,在“某公司”裡,存在著三份變更清單:

1)預生產清單;2)生產中清單;3)生產後清單。

預生產變更清單:

  1. 在團隊日曆當中安排 push 至生產環境的時間。

  2. 對 PR 進行審查與批准。

  3. 相關團隊測試並批准各項變更。

  4. 由指定的測試人員確保所有測試用例均正常通過並符合要求。

  5. 提交申請與測試或者生產環境無衝突。

  6. 在台式機及移動設備上運行冒煙 / 回歸測試。

生產中變量清單:

  1. 發布流水線中無即時事故(build 失敗)。

  2. 在此期間,不存在其他部署申請。

  3. 在 Slack 中通知部署已經開始。

  4. 按下“紅色按鈕”開始 push 至生產環境。

生產後變更清單:

  1. 檢查部署腳本日誌以了解部署是否成功。

  2. 在台式機及移動設備上運行冒煙 / 回歸測試。

  3. 以一小時為周期,監控所有日誌及統計圖表。

  4. 通知變更相關團隊。

  5. 如果沒有問題,在 Slack 當中宣布部署成功。

很明顯,其中不少步驟都有可能發生問題,而產生問題的原因就是執行人員看不到環境的整體情況。

下面,我們從三個角度提出問題:

  1. 為什麼會出問題?

  2. 本應以怎樣的方式加以預防?

  3. 某公司在事後做了什麼?

事故分析

先來看看問題為什麼會發生,又怎麼會導致大量日誌丟失。

  1. PR 當中包含對某行代碼的微小變更,可能影響巨大,但相關描述非常模糊。

  2. 在同一 PR 當中,三分之一的程序流並未經過王二的實際測試,因為他覺得抽查當中沒發現問題就夠了。

  3. 代碼審查員沒有註意到這一微小的變更。

  4. 王二並沒有在測試中檢查這些即將受到毀滅性影響的日誌(注意:公司當中根本沒有質量保證人員這一職務)。

  5. 身兼測試人員與代碼審查員兩職於一身的王二沒有註意到代碼未發送記錄請求的問題(注意:因為沒有質量保證人員,所以代碼審查員必然同時兼任測試人員)。

  6. 在 push 至生產環境後的監控環節當中,由於圖表中的變化與其他長期記錄相比非常微小,所以日誌記錄丟失問題沒能被及時發現。

  7. 不存在對日誌及圖表的每日監控機制,因此沒人第一時間注意到這個問題。

  8. 下一條部署至生產環境的 PR 同樣沒有正確遵循檢查流程,且 / 或測試人員與第 6 條一樣未能發現圖表中的微小變化。

本應以怎樣的方式加以預防

本應執行週期為七天的日誌檢查

開發人員往往只會檢查發布過程中 1 到 4 小時周期內的日誌。這就意味著,如果 4 小時前生產系統曾經發生某些異常,那麼測試人員將無法發現。理論上來說,這樣的問題忽略循環可能永遠存在。

事實上,王二在後來對七天內日誌記錄進行檢查時,發現了多項錯誤。但到目前為止,某公司還沒有任何人——從管理層到開發人員——認為有必要把監控週期設定為七天。時間本身也並不是問題,開發人員可以快速切換日誌顯示區間來查看過去 1 小時、1 天或者 7 天當中的記錄內容。

每日監控

目前公司內缺少適當的每日監控機制。監控只在清單列出的變更週期內進行,此外不再單獨投入時間。一種解決辦法,就是在每天工作結束時強制要求檢查日誌內容。

我在拉脫維亞的 Evolution Gaming 公司工作時,團隊採取的是職務輪換機制,大家輪流負責檢查 Sentry 錯誤日誌、審查待處理的 PR、向團隊通報審查結果以及進行日常維護等等。在整個衝刺週期(2 週)當中,大家一直身兼常規職務與每日輪換職務兩種角色。為了確保所有要求都能得到嚴格遵循,我們還在流程中添加了一種遊戲化機制,即每一次 PR 審查都對應一定的分數獎勵。

雖然大家都承認審查工作的意義,但在實際執行中,大多數人其實並不情願把時間“浪費”在這件事上。因此,激勵制度是種提升審查參與度與積極性的好辦法。

某公司也可以在監控方面採用相同的方法。畢竟對大多數企業來說,用稍稍放慢一點速度的代價換取更高的發布質量無疑是划算的,而且從長遠來看對每個人都有利。

雙批准或者多批准模式

在我待過的團隊中,但凡代碼審查效果出色的,都至少要對每條 PR 進行雙重批准。事實證明,這種多輪把關的方法相當可靠,因此我後來自己單飛後,也繼續在個人外包合同中繼續沿用這一模式。

單批准模式的主要問題在於……舉個例子,大家見過只有一名飛行員的航班嗎?萬一其中一人忘記開啟控制面板上的某個開關,至少還有另一位及時提醒提醒。

提高測試質量

無論是單元測試還是集成測試,我們都該將其視為保證生產安全的一道有力屏障。

很明顯,某公司內的集成測試並沒有覆蓋到代碼中的重要變更部分。事實上,這家公司壓根就沒把集成測試納入王二團隊的測試範疇,甚至多次打回了要求進行集成測試的申請。這並不是因為集成測試在技術上有多困難,只是因為上下級溝通不暢、缺乏主動性以及無知。

從主觀上講,大多數開發人員不願進行測試的頭號原因,是“我為什麼要在代碼已經能夠正常運行的情況下,因為「不必要的麻煩事」而拖慢部署進度? ”

而第二號原因,就是截止日期被壓得太緊。

說起單元測試,很多朋友都覺得這就是走個過場,畢竟我們很難量化單元測試到底阻斷了多少錯誤。

這裡要強調一下,單元測試確實可以防止開發期間出現的大量錯誤。我們的項目需要在 push 至生產環境之前就進行過測試,這樣即使 1 項測試失敗,合併嘗試都將中止。這就給了我們解決問題並再次 push 的時間窗口。

更重要的是,我們可以在測試運行期間嚴密觀察。我發現很多開發人員沒有養成在後台運行測試的好習慣,而由此帶來的惡果,就是在問題出現後大家需要耗費大量時間回溯一切變更。

當然,即使是在觀察當中,出現的大量問題也有可能讓我們迷失在修改的漩渦當中。這個問題可以通過科學的重構方法進行預防。 Martin Fowlers 在書中建議以積極的心態進行重構,例如在發生測試失敗時,請還原最後一項變更並確保不影響測試環境。他提出的方法可以概括為“一次只改一行”。當然,我們也可以根據實際情況靈活調整,比如“一次只改兩行”之類。

接下來是 TDD。有研究表明,TDD 能夠幫助我們將項目中的 bug 減少 40% 到 90%。但這種從長遠來看收益豐厚的方法,卻往往遭到開發人員的激烈反對。不過如果各位不打算破產或者失業,還是請把 TDD 嚴格貫徹到位。較高的測試標準,也將為您帶來更強的比較競爭優勢。

實施集成測試

當然,單元測試可能很難覆蓋到某些代碼流,甚至相關測試更像是集成 / 單元測試的結合體。這確實是種比較棘手的情況,這裡我推薦大家了解 Sandy Metz 給出的建議:

事故复盤:我們是怎麼弄丟1400萬條日誌記錄的 1

如果能在開發過程中節約成本,不妨打破規則。 ——Rails Conf 2013 大會,Sandy Metz

我可能會考慮在單元測試中採用集成測試方法來覆蓋這次引發問題的代碼行,從而盡量縮小範圍。對問題進行充分記錄,並在測試完成後儘快清除集成測試部分。

當然,我並不是集成測試方面的專家,但在這種情況下,集成測試確實能夠防止數據日誌丟失。

極限掌控力

我想聊的最後一點,來自 Jocko Willinks 的《極限掌控力》一書。我堅信,變更清單檢查不足的責任,有相當一部分要由團隊主管(包括技術主管或者項目經理)以及開發人員本身來承擔。

開發人員與質量保證人員之所以沒有嚴格遵循檢查流程,是因為他們並不清楚這項工作的重要意義,以及一旦出現偏差有可能帶來怎樣的嚴重後果。當然,這也可能是因為主管人員不太了解流程中的某些組成部分。在這種情況下,開發人員與質量保證員必須抱有“一路上報,直到解決問題”的堅定態度。

某公司在事後做了什麼

在這條 PR 被 push 至生產環境的七天之後,某公司決定開髮用於比較服務器與客戶端日誌內容的系統。這套系統能夠在服務器與客戶端日誌間存在差異時及時發出通報。

王二團隊開始引入每週日誌監控流程,但是主管仍然沒有親自跟進這項工作。

幸運的是,公司管理層已經開始進行對話,探討如何解決未來可能出現的類似問題。

在業務流程改善方面,存在著大量可供企業選擇的具體方案,而這些方案能夠有效預防代碼引發的內部或者外部事故。但遺憾的是,我們人類天然具有一種懶性或者說消極性,那就是只會在遭遇災難之後才真正意識到預防準備的重要意義。

原文鏈接

We Crashed and Lost All Essential Data Logs. Where Did We Go Wrong?