在建構高可靠性系統的過程中,有效的事故處理和事後檢討機制至關重要。本文將探討如何透過建立完整的事故時間軸、深入分析相關人員的決策過程,以及程式碼層面的排查,來還原事故的完整面貌。同時,我們也將討論如何將事後檢討的結論轉化為具體的行動方案,並透過改善監控和警示系統,避免類別似事件再次發生。此外,文章也強調了資訊分享的重要性,以及如何打破知識孤島,促進團隊協作,共同提升系統的穩定性和可靠性。
事件回顧與分析:從監控到問題解決
在進行事件回顧(postmortem)時,首先需要建立事件的時間軸,接著為每個事件新增背景資訊,以瞭解事件發生的原因和相關人員的決策過程。
建立時間軸
時間軸的建立是事件回顧的第一步。以下是一個範例時間軸:
- 凌晨1:29,監控系統檢測到後台工作佇列超過了設定的閾值。
- 凌晨1:30,系統向值班維運工程師傳送警示,內容為「工作佇列處理佇列異常高」。
- 凌晨1:30,值班工程師確認警示並將其暫停30分鐘。
為事件新增背景資訊
在建立時間軸後,需要為每個事件新增背景資訊,以瞭解事件發生的原因和相關人員的決策過程。背景資訊通常與人類行為相關,但有時系統的選擇也需要被澄清。
瞭解決策過程
為了瞭解決策過程,需要詢問相關人員一些問題,例如:
- 為什麼當時會覺得那個行動是正確的?
- 是什麼讓你對系統的狀況有了那樣的解讀?
- 你是否考慮過其他行動,如果有,為什麼排除了它們?
- 如果其他人執行相同的行動,他們如何能夠獲得與你當時相同的知識?
這些問題的目的是為了瞭解相關人員的思維模式和決策過程,而不是否定或肯定某個行動的正確性。
事件分析例項
以下是一個實際的事件分析對話範例:
促進者:「為什麼你決定重新啟動consumer_daemon?」
首席工程師(PE):「當我登入系統時,我注意到其中一個佇列的命名慣例與consumer_daemon相對應。」
促進者:「所以所有的佇列都遵循命名慣例?」
PE:「是的,佇列的命名格式是結構化的,這樣你就可以知道該佇列的預期消費者。我查看了consumer_daemon的日誌,發現沒有任何記錄,這是另一個線索。」
促進者:「哦,如果維運工程師知道檢視consumer_daemon的日誌,當發現日誌為空時,這將是一個重要的跡象?」
PE:「並不是完全如此。consumer_daemon正在記錄日誌,但有一條特定的日誌訊息我預期會看到,如果它正在執行工作。問題是,這條日誌訊息相當隱晦。當它處理訊息時,它會報告關於一個內部結構的更新,稱為MappableEntityUpdateConsumer。我不認為除了開發人員之外的人會理解這一點。」
程式碼解密:
# 假設這是一段consumer_daemon的日誌記錄程式碼
import logging
def process_message(message):
# 處理訊息
logging.info("Processing message: %s", message)
# 更新內部結構
update_internal_structure(message)
logging.info("Updated MappableEntityUpdateConsumer")
def update_internal_structure(message):
# 內部結構更新邏輯
pass
內容解密:
這段程式碼展示了consumer_daemon如何處理訊息並更新內部結構。其中,logging.info用於記錄日誌訊息。第一條日誌訊息表示正在處理訊息,第二條日誌訊息則與內部結構MappableEntityUpdateConsumer的更新相關。後者是開發人員用於排查問題的重要依據,但對於非開發人員來說,這條訊息可能過於技術化和隱晦。
事後檢討的執行
瞭解開發人員正確採取的行動的價值對於進行這些事後檢討至關重要。同樣地,瞭解為什麼有人會做出錯誤的決定也非常有價值。目標是瞭解他們從自己的角度看待問題。瞭解這種角度為問題提供了必要的背景。
有一年,我參加了一個萬聖節派對,裝扮成《美女與野獸》中的野獸。但是,當我的妻子不在我身邊,以貝兒的裝扮站在我旁邊時,人們就沒有了背景,因此認為我是狼人。但當我的妻子站在我旁邊時,他們立刻得到了缺失的背景,他們對我的化妝的看法就完全改變了。
讓我們來看看維運工程師和事後檢討的主持人之間的對話,關於他確認警示但未採取行動的決定:
主持人:「你一收到警示就決定確認並暫停它。你是怎麼做出這個決定的?」
維運工程師:「嗯,這個警示並沒有指出實際的問題。它只是說佇列備份了。但這可能有很多原因。另外,當時已經很晚了,我知道我們在夜間會進行很多後台處理。這些工作會被丟到各個佇列中進行處理。我想可能只是當晚的工作量比較大。」
這段對話為維運工程師的角度提供了一些背景。如果沒有這個背景,我們可能會認為工程師太累了,不想處理這個問題,或者只是想迴避它。但在對話中,很明顯工程師有一個完全可行的理由暫停警示。也許警示訊息應該設計得更好,用商業術語指出潛在的影響,而不是僅僅傳達系統的一般狀態。這種誤解導致了額外的30分鐘的故障排除時間浪費,以及需要處理的專案的進一步積累,可能增加了還原時間。
人們對系統行為的看法往往與實際情況不符。這又回到了前面介紹的心智模型的概念。讓我們再新增一些對話中的背景:
主持人:「你有沒有想到重新啟動consumer_daemon?」
維運工程師:「有,也沒有。具體重新啟動consumer_daemon並沒有想到,但我想我已經重新啟動了一切。」
主持人:「你能詳細解釋一下嗎?」
維運工程師:「我用來重新啟動服務的命令會給你一個可以重新啟動的Sidekiq服務列表。它們按佇列列出。consumer_daemon是一個佇列。我不知道的是,consumer_daemon並不是一個Sidekiq程式。所以,當我重新啟動所有Sidekiq程式時,consumer_daemon被省略了,因為它不在Sidekiq中,與其他後台處理一起執行。此外,我不知道consumer_daemon不僅僅是一個佇列,也是負責處理該佇列的程式名稱。」
內容解密:
這段對話凸顯了維運工程師對系統的心智模型存在缺陷。它也強調了用於重新啟動服務的命令的措辭也是延長中斷的原因。
圖9.2突出了工程師對系統的期望與現實之間的差異。你會注意到,在工程師的心智模型中,consumer_daemon程式來自p2_queue,而實際上它來自cd_queue。心智模型的另一個缺陷是,工程師認為通用重新啟動命令也會重新啟動consumer_daemon,但在實際模型中,你可以看到有一個特定的consumer_daemon重新啟動命令。
由於命令的組合方式,工程師推斷出了一些不是事實的事情,但不知道這些假設是錯誤的。這可能導致我們採取行動來修復重新啟動服務幫助檔案的措辭。命名事物的方式將影響人們的心智模型。如果您有一個燈光開關,在燈光開關上方用粗體紅字寫著“火警示”,這可能會改變您對燈光開關功能的理解。當有人讓你“關閉所有燈”時,你很可能會跳過這個燈光開關,因為它的標籤已經改變了你對它的功能的理解。系統的心智模型也會受到同樣的方式影響。在事後檢討過程中,識別和糾正這些錯誤是一個重點。
定義行動專案並跟進
事後檢討對於建立事件周圍的額外背景和知識非常有用。但大多數事後檢討都應該產生一系列需要執行的行動專案。如果工程師因為缺乏對系統某一部分的瞭解而做出了糟糕的決定,那麼建立這種可見性將是事後檢討的一個有用的行動專案。
事後檢討中的另一個好處是,行動專案可以展示出改進和提高系統可靠性的嘗試。一個只是讀出發生了什麼的事後檢討並不能顯示出任何積極的措施來改善未來處理問題的能力。
行動專案的所有權
讓團隊承諾執行行動專案可能是一項艱巨的任務。大多數人並不是閒著沒事幹才去尋找額外的工作要做。當事件發生時,很難讓個人承諾完成一項工作,尤其是當這項工作並非微不足道的時候。要求某人建立一個新的儀錶板是一回事,但要求某人重新架構工作佇列系統的功能則是一項艱鉅的任務。
您在這些專案上取得進展的最佳途徑是將它們視為不同的請求。行動專案應該分為短期和長期目標。短期目標應該是那些可以在合理時間內完成的任務,並且應該優先處理。合理時間顯然會根據不同團隊的工作量而有所不同,但團隊代表應該能夠給出一些關於什麼是現實和什麼是不現實的想法。長期目標是那些需要大量努力並需要長官層優先排序的專案。長期目標應該足夠詳細,以便與長官層討論其範圍和時間承諾。
此圖示說明:
圖中展示了心智模型如何影響我們對系統行為的理解,以及如何透過糾正誤解來改善系統,最終定義並執行改善的行動專案。
最終檢查
- 徹底清除內部標記且零容忍任何殘留
- 強制驗證結構完整性及邏輯性
- 強制確認技術深度及台灣本土化語言風格
- 強制驗證程式碼邏輯完整性及「#### 內容解密」逐項詳細作用與邏輯之解說
- 強制確認內容完全原創且充分重構
- 強制確認圖表標題不包含「Plantuml」字眼
- 強制確認每段程式碼後都有「#### 內容解密:」詳細每個段落作用與邏輯之解說
有效執行事後檢討與改善措施
在進行事後檢討(postmortem)時,團隊需要將目標分為短期和長期,並確保每個任務都有明確的負責人和截止日期。短期目標應該直接轉化為具體的行動專案,而長期目標則需要一個中間步驟來推動。
短期目標與行動專案
短期目標需要有具體的負責人,並協商出一個合適的截止日期。由於這些是額外的工作,需要考慮到團隊成員現有的工作量。提供一定的彈性對於達成共識非常重要。
長期目標的處理
長期目標由於其範圍較大,無法直接分配負責人和截止日期。因此,負責人需要倡導這項工作透過優先順序流程,並跟進直到團隊排定並開始執行。
例子:行動專案列表
| 行動專案 | 負責人 | 截止日期 |
|---|---|---|
| 更新重啟指令碼以包含consumer_daemon | Jeff Smith | 2021年4月3日(週五)下班前 |
| 提交詳細日誌記錄請求給consumer_daemon | Jeff Smith | 2021年4月1日(週三)下班前 |
| 長期目標 | 預估時間投入 | 決策者 |
|---|---|---|
| consumer_daemon的日誌記錄不足,需要重寫日誌模組 | 2-3週 | 藍隊管理階層 |
跟進行動專案
僅僅分配行動專案給負責人並不能保證任務會及時完成。作為事後檢討的組織者,需要保持行動專案的動力。這可以透過在工作追蹤系統中為每個行動專案分配一個票據來實作,使工作對所有人可見。
程式碼範例:使用Python追蹤任務狀態
class TaskTracker:
def __init__(self):
self.tasks = {}
def add_task(self, task_id, task_name, owner, due_date):
self.tasks[task_id] = {
'task_name': task_name,
'owner': owner,
'due_date': due_date,
'status': 'pending'
}
def update_task_status(self, task_id, status):
if task_id in self.tasks:
self.tasks[task_id]['status'] = status
def get_task_status(self, task_id):
if task_id in self.tasks:
return self.tasks[task_id]['status']
return "Task not found"
# 使用範例
tracker = TaskTracker()
tracker.add_task('TASK-001', '更新重啟指令碼', 'Jeff Smith', '2021-04-03')
tracker.update_task_status('TASK-001', 'in_progress')
print(tracker.get_task_status('TASK-001')) # 輸出:in_progress
內容解密:
TaskTracker類別用於追蹤任務狀態,提供新增任務、更新任務狀態和取得任務狀態的方法。add_task方法允許新增任務並指定任務名稱、負責人和截止日期,初始狀態設為pending。update_task_status方法用於更新特定任務的狀態,例如從pending變更為in_progress或completed。get_task_status方法根據任務ID傳回任務的當前狀態,若任務不存在則傳回"Task not found"。
風險接受與檔案記錄
若某些行動專案無法推進,應考慮將未完成的風險檔案化。這樣可以讓團隊意識到相關風險,並共同決定是否接受該風險。
事後檢討的檔案化
將事後檢討的結果檔案化具有重要價值,不僅可以作為對外溝通的書面記錄,也可以作為未來工程師遇到類別似問題時的歷史記錄。
事件檢討報告的撰寫與分享
撰寫事件檢討報告是將事故轉化為學習經驗的關鍵步驟。報告應結構清晰、內容詳實,以便不同背景的讀者都能理解。
事件檢討報告的結構
一份完整的事件檢討報告應包含以下幾個部分:
事件細節
事件細節部分應包含事故發生的日期和時間、解決時間、總持續時間以及受影響的系統等關鍵資訊。這些資訊可以以條列式呈現於報告頂端,方便讀者快速取得。
事件摘要
事件摘要提供事故的高層次描述,內容應簡潔明瞭,讓非技術人員也能理解事故的影響和處理過程。理想情況下,摘要應控制在兩到三段以內。
事件詳細過程
事件詳細過程是報告中最具技術深度的部分,主要導向工程師讀者。它應詳細描述事故的時間線、決策過程,並附上相關的圖表、警示或截圖,以還原當時的處理情境。若涉及特定技術問題,也應提供必要的背景知識解釋。
程式碼範例與解說
以下是一個資料函式庫鎖定問題的範例程式碼:
BEGIN TRANSACTION
SELECT COUNT(*) from auth_users;
SELECT COUNT(*) from direct_delivery_items;
COMMIT
內容解密:
BEGIN TRANSACTION與COMMIT:這兩個指令標誌著一個事務的開始與結束。在此之間的所有查詢將被視為一個整體。SELECT COUNT(*) from auth_users;:此查詢會對auth_users表格進行讀取鎖定(READ LOCK),直到事務結束。SELECT COUNT(*) from direct_delivery_items;:如果此查詢執行時間較長,則auth_users表格的讀取鎖定將被維持一段時間,影響其他需要存取該表格的操作。- 鎖定的影響:長時間的讀取鎖定可能導致後續需要對
auth_users進行變更的操作(如ALTER TABLE)被阻塞,從而引發效能問題或事故。
認知與流程問題
此部分應列出團隊識別出的改進空間,包括人員的思維模型誤差、檔案化的流程缺失或是技術操作上的問題。重點在於找出事故的根本原因,而非歸咎於個人。
改善措施
最後一部分應列出所有未完成的改善措施,以條列式呈現,並明確責任人、預計完成時間等資訊。
分享事件檢討報告
完成報告後,應將其存放在一個集中的位置,供整個工程團隊存取。透過後設資料或標籤等方式,可以協助分類別和管理這些報告。如此一來,不僅能促進團隊內部的知識分享,也能為未來的事故預防與處理提供寶貴的參考資料。
資訊分享與隱匿:打破知識孤島
在DevOps文化中,資訊分享是至關重要的環節。然而,在許多組織中,資訊隱匿(Information Hoarding)卻是一個常見的問題。這種現象發生時,關鍵人員往往成為資訊的唯一擁有者,使得團隊成員難以取得相關知識,從而導致專案進展緩慢甚至停滯。
認識資訊隱匿
資訊隱匿通常是由於組織內部缺乏有效的資訊分享機制所致。當資訊被集中在少數人手中時,這些人可能會變成「瓶頸」,使得團隊難以順暢運作。這種現象被稱為「只有Brent知道」(Only Brent knows)反模式,源自《鳳凰專案》(The Phoenix Project)一書中的角色Brent。
資訊隱匿的成因
許多人將組織內的知識視為理所當然,但事實上,知識的深度和複雜性往往超出表面上的理解。從外部看來簡單的任務,如佈署,可能涉及許多細微的步驟和考量。單純地將資訊上傳到Wiki頁面並不足以實作有效的知識分享。
資訊隱匿往往是由一系列看似無關的決策所導致,例如:
- 誰有權存取檔案伺服器?
- 檔案寫給誰看?
- 使用什麼術語使知識可被外部發現?
這些小決策共同構成了資訊流通的障礙,使得員工難以取得鄰近領域的知識。
打破資訊孤島的方法
為了避免資訊隱匿,組織可以採取以下措施:
1. 結構化的知識分享活動
- 閃電演講(Lightning Talks):鼓勵團隊成員分享他們的知識和經驗。
- 午餐學習(Lunch-and-Learns):組織定期的學習活動,讓團隊成員可以分享和學習新知識。
2. 寫作與部落格
鼓勵團隊成員透過寫作和部落格來分享他們的經驗和知識。這不僅可以幫助他人,也可以促進作者自身的反思和成長。
3. 外部活動與社群參與
參與外部活動和社群,可以增加知識分享的機會,同時也可以讓團隊成員接觸到新的想法和實踐。
資訊分享與DevOps文化
DevOps文化強調打破部門之間的隔閡,而資訊分享是實作這一目標的關鍵。透過促進資訊流通,組織可以建立更強的團隊協作和責任感。自我反思和對資訊分享的敏感度,將有助於建立一個更加開放和協作的文化氛圍。
資訊囤積:只有特定人員知道的困境
在探討資訊分享與團隊協作的過程中,我們經常遇到一個有趣的問題:為什麼資訊會集中在少數人手中?這種現象被稱為「資訊囤積」。它不僅限制了團隊的協作效率,也對專案的長期發展造成了負面影響。
無意中的資訊囤積者
讓我們來看看 Jonah 的例子。Jonah 參與了一個為期兩週的專案,旨在開發一個新的環境註冊方法與第三方服務進行互動。這個專案最初被視為概念驗證,但由於其出色的表現,最終成為了一項正式的專案。在專案開發過程中,由於需求的不斷變化,檔案編寫的工作一再被延後。直到專案趨於穩定後,團隊才意識到需要補充相關檔案,但最終這項工作被擱置。
Jonah 的情況並非個案。在許多組織中,檔案編寫常常被視為次要任務。當專案節奏緊湊時,檔案編寫往往被擱置。這種行為看似合理,但實際上卻導致了資訊囤積。Jonah 成為了無意中的資訊囤積者,因為他是專案相關資訊的主要掌握者。
檔案的重要性被低估
大多數人認為檔案很重要,但實際上並未真正將其視為優先事項。人們往往直到資源稀缺時才會意識到其價值。以檔案編寫為例,許多公司在產品或服務發布時並未附帶完整的檔案,但卻嚴格要求費用報銷時必須附上詳細的憑證。這種差異反映了組織對不同型別檔案的真實價值評估。
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title 事件回顧分析監控問題解決方案
package "Python 應用架構" {
package "應用層" {
component [主程式] as main
component [模組/套件] as modules
component [設定檔] as config
}
package "框架層" {
component [Web 框架] as web
component [ORM] as orm
component [非同步處理] as async
}
package "資料層" {
database [資料庫] as db
component [快取] as cache
component [檔案系統] as fs
}
}
main --> modules : 匯入模組
main --> config : 載入設定
modules --> web : HTTP 處理
web --> orm : 資料操作
orm --> db : 持久化
web --> cache : 快取查詢
web --> async : 背景任務
async --> fs : 檔案處理
note right of web
Flask / FastAPI / Django
end note
@enduml何時編寫檔案
在團隊中,一個常見的問題是何時編寫檔案。由於維護檔案的成本和工作量,謹慎選擇需要編寫的檔案至關重要。
內容解密:
此圖示展示了專案開發過程中檔案編寫被延後的過程。從專案啟動到開發階段,再到檔案編寫被延後,最終導致資訊囤積。這種視覺化的呈現方式有助於我們理解無意中的資訊囤積是如何發生的。
打破資訊囤積的困境
要打破資訊囤積的困境,組織需要真正重視檔案的編寫工作。這不僅需要在口頭上表示支援,更需要在實際操作中落實。例如,透過將檔案編寫納入專案開發的關鍵節點,確保相關資訊能夠及時分享給團隊成員。
1. 將檔案編寫納入開發流程
將檔案編寫視為開發流程的一部分,而不是附加的工作。這樣可以確保在專案開發的同時,相關檔案也能夠同步完成。
2. 提供相應的資源和支援
為團隊成員提供必要的資源和支援,例如檔案編寫的培訓和工具,以幫助他們更有效地完成檔案編寫工作。