為什麼維運是隱形稅賦#
Instagram 共同創辦人兼 CTO Mike Krieger: 「你所增加的每一項技術,在數學上都保證了隨時間推移一定會出錯。 到了某個時間點,維運這些技術會耗盡整個團隊的精力。」
Instagram 在 2010 年上線到 2012 年被 Facebook 收購時,使用者已成長到 4,000 萬,當時團隊卻只有 13 個人,用戶/員工比例超過 300 萬比 1。 他們的關鍵心法之一就是 最小化維運負擔:拒絕在生產環境中使用未經驗證的新技術,固守 PostgreSQL、Memcache、Redis 這些穩定、易理解的選項。
系統成本不是在上線那一刻停止累積,而是「才剛開始累積」。 每天花在維運、修 Bug、教新人的時間,都是無法投入其他高槓桿活動的機會成本。
1. 擁抱維運的簡單性(Embrace Operational Simplicity)#
Steve Jobs 談 iPod 設計:「人們遇到問題時,往往停在第一個複雜的解決方案。但如果你像剝洋蔥般持續深入,你常常能找到非常優雅且簡單的解法——大多數人只是沒有投入夠多時間和精力到那一步。」
Instagram 的前身 Burbn 是一個結合打卡、互動、照片分享的 App,做了一年後因為「太複雜、太雜亂」被砍光,只保留照片、留言與點讚——這就是 Instagram 的誕生。
反例:Pinterest 早期過度複雜#
兩年內成長到每月百億 PV 的過程中,Pinterest 三人工程團隊資料/快取層曾同時使用 七種技術:MySQL、Cassandra、Membase、Memcache、Redis、Elastic Search、MongoDB。 過度複雜的架構帶來四種成本:
- 專業知識被切割分散到多個系統
- 單點故障增加:每個元件都是一個潛在故障點
- 新人學習曲線陡峭
- 工具與抽象的投入被稀釋:沒有任何一個系統能被好好支援
最終他們簡化到只用 MySQL、Memcache、Redis、Solr,並透過「水平擴展同類元件」而非「引入新元件」來支撐後續 4 倍以上的成長。
落地原則#
- 新語言:副專案實驗 OK,但放上正式環境前先問:團隊熟嗎?好招人嗎?
- 資料儲存:新潮 NoSQL 承諾解決 MySQL/PostgreSQL 的痛,但維運成本是否真的更低?
- 重用 vs. 自造:與其各場景挑「最合適工具」,不如想想「多元件帶來的維運複雜度」是否大於標準化的收益
- 分散式 vs. 單機:在引入叢集前先確認資料是否真的大到需要
2. 建構「快速失敗(Fail Fast)」的系統#
許多工程師誤以為「Robustness(健壯)」=「永不崩潰」,因此把錯誤吞掉或預設值帶過。 這反而導致軟體 「緩慢失敗(Fail Slowly)」,Bug 難以追蹤。
Jim Shore 在《Fail Fast》一文:「Fail Fast 看起來會讓你的軟體更脆弱,但實際上會讓它更健壯——Bug 更容易被發現與修復,所以更少進到生產環境。」
常見的 Fail Fast 技巧#
- 啟動時崩潰:遇到設定錯誤直接停止,而非帶著錯誤參數執行
- 嚴格驗證輸入:在資料被消費前就攔截錯誤
- 拋出例外:當關鍵資料結構損壞(如 Iterator 失效)時立即拋出
- 使用斷言(Assert):在複雜邏輯前後確保關鍵不變量成立
- 向上拋出無法處理的外部錯誤:不要吞掉(Swallow)
- 越早告警越好:偵測到不一致狀態就通知工程師
真實事故案例#
作者親身經歷的一個 Bug:Web 請求 timeout 後,沒有正確重置 MySQL 連線;下一個請求重用了同一條連線,回傳結果竟是上一個請求的內容。 如果一開始就採用「連線使用前 assert 乾淨」這類 Fail Fast 設計,原本一週的災難只需幾分鐘就能定位。
Fail Fast 不等於把錯誤直接拋到使用者眼前。可採用混合策略: 元件內部 Fail Fast,但搭配全域 Exception Handler 紀錄錯誤、優雅降級給使用者,再用儀表板依錯誤頻率排序,讓工程師優先處理高頻問題。
3. 無情自動化機械式任務(Relentlessly Automate)#
工程師常面臨判斷:「手動執行的總時間」 vs.「自動化的前期成本」。 我們會自動化得太少,常見原因:
- 沒時間:迫於期限,犧牲長期利益換取短期交付
- 公地悲劇(Tragedy of Commons):手動工作分散在多人輪值上,沒有單一人有足夠動機解決
- 工具不熟:缺乏 Shell Script 或系統整合的技能——但這正是練越多越快的技能
- 低估頻率:誤以為這項任務不會常做
- 低估時間複利:每次省 10 秒 × 每天 10 次 = 一年快一個工作日
適合自動化的任務#
- 程式碼/系統行為驗證
- 資料提取、轉換、摘要
- 偵測錯誤率異常
- 建置與部署
- 資料庫快照與還原
- 定期批次計算
- 重啟 Web 服務
- 風格 Linter
- 訓練機器學習模型
- 帳號/使用者資料管理
- 增減服務群組中的伺服器
自動化「機械」 vs. 自動化「決策」#
Facebook 前基礎設施工程總監 Bobby Johnson 把自動化分成兩種:
- 機械式自動化:執行一連串步驟,相對直接、可測試
- 決策式自動化:要在發生狀況時自我修復、做出正確判斷——困難很多
「我們最糟的幾次大故障,都是來自決策式自動化跑出失控行為——它們很少被好好測試,因為照定義就是在不尋常的情境下執行。」
Facebook 早期管理上千個 MySQL shard,「機械式」部分(搬移 shard 的命令)已自動化,但「決策式」部分(要把哪些 shard 搬到哪)仍由一位工程師手動執行多年,直到後來才推出 MySQL Pool Scanner 進行自動再平衡。
先把所有機械式任務的低垂果實摘完,再投資決策自動化。
4. 讓批次流程具備冪等性(Idempotence)#
冪等性:指一個操作無論執行一次還是多次,產生的結果都是一樣的。
當自動化規模變大,部分腳本因網路波動或外部服務暫時失敗的機率也會變大。 若批次流程冪等,就能放心地「重試(Retry)」——不擔心產生重複資料或副作用。
範例:每週使用者行為計數#
- 非冪等做法:直接掃 log、把每筆事件加進週計數器。若中途失敗,重跑會把某些事件多算一次
- 冪等做法:先以「日」為單位算出當日計數,再加總七天得到週計數。重跑只是把當日值覆蓋一次,不會雙重計算
當完全冪等做不到時,至少做到「可重入(Reentrant)」:被中斷後再執行能順利完成。 讓每個任務「要不全部成功,要不全部失敗」。
額外好處:把不常跑的流程也常跑#
Dropbox 前工程師 Rajiv Eranki 建議:把「每月才跑一次」的腳本,設成每天乾跑(Dry Run)。 這樣假設出問題,當週就會被發現,而不是月底警報響起時手忙腳亂。
把每 5 到 10 分鐘的系統檢查改成每 60 秒檢查、僅在「連續失敗」才告警,也能過濾掉許多瞬時網路抖動。
5. 快速回應與復原(Respond and Recover Quickly)#
任何足夠複雜的系統都會故障。 與其投資無限資源追求「零故障」,不如投資「快速復原的能力」。
Netflix 的 Chaos Monkey#
Netflix 工程師主動寫了一隻會在生產環境隨機殺掉自家服務的工具 Chaos Monkey。 理由是:「對抗重大意外的最好辦法,就是經常發生小意外。」 他們把 Chaos Monkey 設定在週間工作時段觸發故障,方便工程師在辦公室即時處理,而非在半夜被叫醒。 當 AWS 出現大規模事故時,Netflix 影響甚微;其他公司如 Airbnb、Reddit、Foursquare、Quora 卻 down 了好幾個小時。
各家災難演習#
- Google:每年舉辦 DiRT(Disaster Recovery Testing),模擬地震、停電、整個資料中心或辦公室下線,驗證團隊溝通與系統 failover
- Dropbox:主動對生產環境施加額外負載,提早觸發瓶頸,再關掉模擬流量從容調查
用「腳本」決策關鍵時刻#
舊金山 49 人隊前教練 Bill Walsh 在《The Score Takes Care of Itself》提倡「Scripting for Success」: 預先擬好開賽前 20-25 個進攻 if-then 流程。 因為比賽現場觀眾吼叫、計時器倒數、情緒緊繃——這時候靠臨場判斷往往最糟。 把決策提前到平靜時刻完成,是高槓桿做法。
工程上該預演的「What if」#
- 重大 Bug 被部署上線——多快能回滾?
- 資料庫掛了——多快能切換並恢復資料?
- 流量暴增——能擴展或卸載多快?
- 測試/預備環境壞了——能多快重建?
- 客戶通報緊急問題——通知到工程師需要多久?
也適用於工程以外的議題#
- 主管在重要會議上突然反對產品計畫——你準備好應對問題了嗎?
- 關鍵成員生病、離職——知識怎麼分享?
- 用戶反彈新功能——立場是什麼、多快能回應?
- 專案延誤——多早能偵測?怎麼補救?
重點摘要(Key Takeaways)#
- 先做最簡單的事:簡單的系統易於理解、擴展與維護
- 快速失敗以定位錯誤源頭:別吞錯、別延後失敗
- 優先自動化機械式任務:決策自動化很難正確,謹慎為之
- 追求冪等性與可重入性:讓腳本可以被安全地重複執行與重試
- 預演失敗情境:對「能快速復原」建立信心,才能在開發時更大膽前行