01 January 2015

今天跟大家分享的是有關 Cassandra operation 的不幸事件,雖然很慘,好在這些鳥事都是在 2014 年完結了,2015 沒煩惱。

操作員在凌晨四點被叫醒

上個月真是過得挺淒慘的,凌晨四點被 server 警報吵醒,Cassandra server cpu load 滿載,沒有回應。我查了一遍硬體後確認都沒問題,接著看 log 也看不出所以然,只好忍痛將全部 server 重開。最糟的是重開後居然沒有任何改善,server cpu 還是滿載。哇,這時候腦袋已經呈現一片空白,服務已經中斷一個多小時了,時鐘已經指在凌晨 5 點...

修不好而且完全無助,冷汗直直流。怎麼辦 ! 怎麼辦 ! 怎麼辦 !!?

找不到原因,只好開始修改程式,將服務降級,即然是 Cassandra 狂飆壞了,那就把存取 Cassandra 最繁重的功能先給關了。試改了幾種方法,部署到不同主機分別測試。最後找到關閉某個功能後 Cassandra 就開始回穩。呼~ 服務總算是復活了,雖然是血只剩一半,好歹勉強可用。這時時鐘已經指在 6 點 ...

止血成功,爭取到更多的時間追查問題,後來查到是某筆資料太大造成的,刪掉後就正常了。系統總算是血補到滿了,回復所有的功能。

下錯藥

這次當機的 root cause,直到一個月後才被查出來 (不小心發現),因果關係離得非常遠,待會在說。在還沒發現真正的原因前, 某筆資料太大 一直是我們認定的原因,為了避免一而再的發生,只好重新設計 Model,改變資料在 Cassandra 的儲存方式,花了一週重寫,也設立好 migration 的計畫。但很不幸的,因為經驗不足,我們踩到了 Cassandra durable queue anti-pattern 。好了,因為是在正式的環境下踩到這個雷,上面那齣戲 - "操作員在凌晨四點被叫醒" 在上次出包後又重新上映了一次。下圖是當時 CPU 惡化的圖,真是慘到無以復加。

enter image description here
圖說: Cassandra durable queue anti pattern CPU usage

Root Cause

好了,來開始解釋真正的原因。

  • 出問題的 Table 是使用 Cassandra 的 Super Column 功能,我們公司很早就開始使用 Cassandra 了,但那時還沒有 CQL, Composite Type 這類的東西,想要儲存進階的資料結構只有 Super Column 可用。Super Column 的缺點在於它在讀取資料時,會整批都讀進記憶體,再回傳篩選的結果給 client。如果資料太大時,Cassandra 的 Heap 不夠用,就會出現 Garbage Collection 風暴,CPU/Load 滿載,無法回應。
  • Super Column 現在已經是 Cassandra 的 anti pattern 了。但不管怎樣,我們也用它跑了三年,效率不好是真的,但沒道理三年後才大壞特壞。
  • 在這個 use case 中,我們是讀出資料後,就會把它刪掉,在 Cassandra 裡,刪除等同於新增 (它會新增一筆叫 tombstone 的資料,標記這筆已死)。想當然爾,這個 Table 用久了會非常的 ,效率也會低落。
  • 所以這三四年來,我們的 Cassandra server ,每週都會用 cron 進行定期的 compaction,(compaction 會去除掉 tombstone 和已經被刪的那一筆),這樣每週的 Table 都是很乾淨的,效率可以維持在一定的水準。
  • 9月底時,Linux 界發生了 Shellshock 的大漏洞,而且還連續爆好幾天。那時每天都要看有沒有新的 patch 釋出,有的話就要馬上 patch。
  • 我手上有二十幾台 server ,一次 patch 這麼多台很累,所以就寫了 Ansible 的 playbook,讓這件事自動化。Ansible 是透過 ssh 連到遠端的 server 後,以 sudo 的方式執行指令。我們的 server 有 Ubuntu,也有 CentOS。Ubuntu 預設有 sudo,但 CentOS 沒有,所以我替那些 server 設定了 sudo。
  • 十月初時,我改變 Cassandra 的配置,嘗試將 server 數量減半,看看可不可以撐住 (就 cost down)。這個計畫試運了一個月,我發現 cpu 使用率每週都在增加,到了十月底,已經撐不住了。最後認定實驗失敗,回復原本的 server 量。
  • 十二月時,發生了上面提到的大當機。而十二月中,換上錯的藥,又大當機了一次
  • 十二月底時,在測其他功能時,不小心地發現 cron job 已經停了三個月了。

cron job 停了三個月沒跑了! WTF !!

cron job 停了沒跑,三年來首見,這是怎樣?!後來才發現是我在九月底為了 shellshock 事件,改了 sudo 設定後,crond 就出現權限錯誤,它就不跑了。解決方法很簡單,crond 重啟就行了。但我怎麼知道要重啟啊!所以整個事件的因果鏈是這樣的:

  1. 2014 九月底 Shellshock 事件暴發
  2. 為了自動化 patch 而改 sudo 設定
  3. 造成 cron job 停跑
  4. Cassandra server 沒定期做 compaction,漸漸惡化
  5. 十月初的 Cassandra cost down,掩蓋了惡化的徵兆,我以為惡化是縮編 server 造成的
  6. 到了 12 月時,super column table 已經三個月沒清理,搖搖欲墜,後來果然讀到大一點,髒一點的資料就垮了。

Shellshock 真是害人不淺啊 (苦笑

Lessons Learned

Cassandra 的 super column 在過去是我們選擇它的原因之一,它很好用,但也潛伏著巨大的缺點。當然,Cassandra 官方這二年來已經拋棄它了,也提供更好的解決方案。但是這一次我體會到的,不單單是避開 super column,選用更好的解法而已。而是 - 過去曾經的優點,將會變成今日的缺點。我們很早就導入了 Cassandra,作為先期使用者,身上背負的 technical debt 其實是很巨大的。這筆債務可沒那麼好心,還讓你慢慢付利息,它會在出乎意料的時間點上、在你最脆弱的時候狠狠地咬上你一口。

當然這筆債我們當下出錯時就不打算再背了,決定砍掉重練。只是重練時,下錯了藥又走火入魔一次。上面也提了,這次的下錯藥是踩到 Cassandra durable queue anti pattern,雖然是因為經驗不足造成的,但如果有做壓力測試的話,也不會在線上環境裡出包這麼慘。

平常我都是會做壓力測試的,但這次沒有,原因之一是太過自信了:我想說過去的服務,使用 Super column 效率這麼差的設計都跑的動了,換上更新的設計豈不是游刃有餘 ? 應該沒問題才是。另外一個原因是那時還不知道真正的原因是 cron job 壞掉造成的,只想趕鴨子上架,快點修好。這次我重新學了 欲速則不達 這個千年的教誨,用血淋淋的方式。

最後是 cron job 失常造成系統惡化。這表示 cron job 本身其實也是要被稽核的,這個問題實在是無止無盡。你想嘛,原本 cron job 就常常扮演定期稽核別人的功能了,現在連它自己也要被稽核,那那個 meta-稽核者,又是誰來稽核呢 ? 這是個 2015 年新挑戰,待我研究研究。