效能瓶頸下的選擇傾向

約是在 2014 年前後,我想通了一件事,但一直遲未將想法化為文字。今天終於静下心來作這件事了,我想要說明的是,為何在某些效能瓶頸的關頭,我會主張作深度分析優化,而非立即性擴充資源緩解眼前的問題。

舉電商網站經營為例,資料量主要有會員、訂單、商品等資料,大致可初估資料的複雜度以 n^3 的速度在膨脹,因此所需的運算需求量也以這速度跟著成長。這可用下方這示意圖來表示:

View post on imgur.com

其中X軸為資料量,Y軸為硬體運算量,而運算資源需求則是原點連往A、B、C三點的這條曲線。

當網站逐漸成長,終於來到第一個效能瓶頸點(A) 時,網站又慢又卡、硬體滿載,並已危及正常運作的時候。我們有2個方向的選擇:

《方案一》直接擴充硬體。這可能馬上就解決了(假設是 1 天),相對而言較快速而簡單。

《方案二》分析軟體程式,找出並修正潛在的程式瓶頸。這工程相對耗時長,可能要耗上不少時間(假設是 5-14天),也會有相應的開發成本支出。

因為網站瓶頸會影響營業收入,所以在規避損失的心態下[1],多數人會選擇 《方案一》的路線,直接將資源從 L1 提升到 L2 ,以盡快脫離眼前損失的狀態。接下來發展的狀況大致如下:

1. 很快就擱置了改善軟體效率的念頭 
    A. 因為眼前壓力解除了 
    B. 有其他更重要的事 ( 應付眼前業務、新功能開發、...)·
    C. 非受迫性的額外支出 ( 需要另外撥出預算跟時間 )  

2. 資料量隨時間持續成長,然後又遇到新的瓶頸點 

3. 在瓶頸點再度面臨兩個選擇,但基於 
    A. 業務量較先前更大,相同時間的代價損失並上一次更高
    B. 營運壓力更大,工作人員比先前更加心有餘而力不足 
    C. 先規避眼前立即的損失再說

4. 再度選擇《方案一》,然後重複回到 1. 

最後反覆重覆上述流程,直到獲利跟時間餘裕都被最後吃光,再無法越過某個天花板為止。

那麼,若是一開始在 A 點時,選擇《方案二》的路線會是如何?

從圖中簡單的估計一下 A-P-Q-R 的路線,(假設資料量以每個月 1W 的速度成長),雖然一開始多花了 15 天的時間,但遇到資源關卡 L2 的時間(瓶頸點R) 可能約是 5.5 個月之後。相較之下, A-B-C 的路線,一開始只花 1 天,但可能約 2.5 個月就抵達資源關卡 L2 (瓶頸點B),然後不到 2.5 個月,就抵達資源關卡 L3 (瓶頸點C)。

於是,同樣過了 6 個月的時間,面對相同的資料量,兩者不管是在資源耗用度,或是在時間餘裕度上,都形成了此消彼長的差距。而這差異,其實早在第一個瓶頸點 (A) 當下的選擇就決定了。

最早我以為,有很大的可能,會在 A-B-C 的過程中,轉變另一個選擇。但是,當我不只一次遇到,已經發展到 C 狀態的苦主們,在討論解法時仍不死心的一再追問說”有沒有更快的方法”的時候,我就深深明白到,人的選擇是有慣性的。一旦選擇《方案一》的路線,除非個性或環境條件有變化,否則就只會一直選擇《方案一》下去,直到被迫不得不改變為止。

同樣的,選擇 《方案二》 的路線,一旦獲得了成功經驗後,也會持續的選擇 《方案二》 的路線下去。唯一不同的是,《方案二》 的路線中,任何一點要切換選擇 《方案一》 相對較容易,所以 《方案二》 比較是一開始選擇較辛苦,但後面的選擇空間是越來越寬裕的路線。

因此,這就是為何在效能瓶頸的問題上,我會主張優先作分析優化,而非立即擴充資源跳過眼前損失的原因。

思考1

“先緩解眼前的問題再說” 有錯嗎?

這没有錯,這是人務實生存的天性,即便我也是如此,而且如果病人已經快要死了,還去關心這個人的儀容整不整齊,也很不合理。所以,危機處理是必要的,我並不否定這件事。

真正的問題點在於,絕大多數的人在眼前的問題緩解後,就將問題給遺忘了。

如果你是個問題緩解後,仍會自發性的長期追蹤跟解決該問題的人,你一點都不需要在意這個問題[2]。但如果你是個問題緩解後,就很快的健忘,然後一再重覆遇到類似問題的人,那麼先忍住待在 “面對眼前問題的損失” 的這個痛苦之中,可能是唯一個能不斷刺激推動你徹底解決問題的唯一動力。

說到這裡,可能有不少人會抗議說:

“痛的是我,又不是你,講的好像很簡單一樣”

是的,完全没錯,這是個困難而且違反直覺的選擇。也正因為真正痛的人不是自己,而是落在作決定跟承擔決定的人身上,所以更需要溝通與說明觀念上的落差。這也是我花這麼多時間寫下這一篇文章的動機之一。

希望讓更多人能正視到,這些簡單決定背後隱藏的長期代價,並理解另一類型的想法為何。而最後不論你認不認同這個想法,最終的判斷跟決定權都還是完全掌握在您的手上(也應該完全承擔),這始終絲毫未變。

如果文中的觀點能帶給你一個新的角度,或是協助你解釋跟說明給另一個人聽,就太好了。
但若是因此在對方未認同下,擅自幫別人作決定而執行的話,我想就不是那麼好了。

也許你是在為別人好,但另一個角度來看,或許也是在迴避”解釋說明跟尋求共識”這條較困難但較長遠的道路。

思考2
另一個近期想通的是,這個想法角度,不只侷限在軟體程式與硬體資源的選擇問題上。
相同的問題也出現在程式開發上的程式架構選擇,開發流程,開發工具,工作方法上…
舉例來說,當我們在努力開發程式時,我們可能會為了跳過程式封裝的限制,而直接複製程式碼片段到我們要用的地方,或是作一些違反常軌的 workaround 。而隨著程式碼片段的複本越來越多,且更新狀態不一致,最後不斷增長的複雜度跟層出不窮的 bug 與斷點,就耗光了程式設計師的腦力跟時間。於是類似的命題或許就會像是:

“要快速拆裝 workaround 完成新功能再說,還是要花更多時間遵循架構,甚至重構改良某些元件?”

其他問題像是:
“要直接加班先出貨眼前的積存訂單,還是要花時間研究找出工作流程中的效率問題,以簡省未來的工作時間?”
“先把眼前這題型背起來再說,還是要花多一點時間了解題目背後原理,以應變未來不同的題目變化?”

在這許不同的事件上,都有類似的瓶頸問題性質。

思考3
這個思維不適用於生活中的所有面向,因為人生有限,每個問題都要這樣分析面對太累了,不可能。

以上,

希望以上幾點分享對大家有幫助。

[1]. 規避損失的傾向參考”展望理論”
[2]. 有趣的是,一個長期追蹤跟解決問題的人,還會遇到上述種種的問題嗎?

曾經遇過的 firefox 與 chrome 的不穩定問題

記得很久之前,(註:2014-09-02),有個問題一直很困擾我。就是我的筆電,只要開機一段時間,就會在某個時間點就會突然產生 CPU 飆高,風扇一直狂轉的狀況。即使把所有應用程式關閉,仍沒辦法穩定下來,直到我重啟 Xorg 視窗系統,才會恢復正常。這個問題,常常無預警的中斷我的電腦操作,搞得我很痛苦。

我試圖找出問題的根源在那裡,大致將可能的嫌疑犯縮小到 Xorg,Firefox,Chrome,qtile(Window Manager) 這4個主程式。不過,一直沒辦法精確的 reproduce 問題的發生。當時,正值 firefox 跟 chrome 在競爭效能之際,我看 top 的資料,都是 firefox 用的資源比 chrome 還多,所以有好一段時間,以為罪魁禍首是 firefox 。

後來,我開始接觸並上手 ganglia/graphite 這類監測工具,有天我想到,之前都是在監看遠端主機的數據,我何不運用這工具直接觀測我自己每天在用的NB呢?! 於是我開始撰寫幾個抓 cputime/memory 的script 跟設定,我直到我後來取得下面這張關鍵數據圖之後,我才明白,原來我的筆電上的問題源頭是 chrome。

View post on imgur.com

從上圖中可以觀察到,Xorg 跟 chrome 的 cputime 在執行到某一個時間點時,就同時出現斜率的變化,跟問題症狀出現的時間也吻合,我這才恍然大悟, 原來禍首是 chrome。而這關鍵證據,也洗清了 firefox 的嫌疑。最後是分析出,是 chrome 有用到 graphic render 加速的功能,關閉後就沒事了。

後來又有一次,大約是 2015/02/20 前後,有更新系統,升級了好些函式庫, firefox 也從 34版升級到 36版,之後就又產生類似的 Xorg reset 的問題。幸運的是,當時我已經建好有觀測數據的機制,再加上有高手在網路上的討論幫忙,最後將分析範圍縮小至 Xorg intel driver 跟 firefox 的 render 加速相關的功能。

View post on imgur.com

其中,由上圖這張當時取得的數據圖中,可以看到 Xorg 的 vesa/intel 驅動程式的不同,及 accel 的 on/off 的一些變化。由此可證實是 Xorg accel 跟其相關的下游應用程式的部份所致。雖然,最終沒能找出是那一段程式導致 mem leak ,但透過更換 driver 並關閉 accel 的選項,暫時避免掉嚴重的 reset 危機。

(註: 後來 firefox 在某一版本更新後,該問題就消失了)

這兩次經驗(還有先前的一些小案例),給我非常非常大的啟發。
以往程式 debug 經驗,多數都依賴 gdb, xdebug, … 或是跑 unit test 來測試程式的問題,這些通常重複操作就能重現一樣的問題。但對於這類正常執行運作一段時間,然後突然崩潰的問題,常常是束手無策。因為,問題發生的那瞬間,我們往往不在現場,也沒有任何除錯紀錄。沒想到,僅僅透過簡單的 cputime/memory 這2道的數據偵測歷史紀錄,對問題的原因分析的幫助會這麼大。

這也讓我意識到,將飛行安全提升到更高等級的關鍵,或許不是風洞測試,而是黑盒子(Flight Recorder)。想想看,一台穩定飛行20年的飛機,出事的時候就只有那瞬間,我們只有一次機會可以補捉相關線索。我想,在時光機發明出來之前,只有透過 Logger 機制,才能迎戰這類 runtime 執行期的問題。這也讓我開始有了 Logger Design Driven 這個想法思考。

最後,再提供一張當時截圖到的 Xorg 在系統升級前後的對照:

View post on imgur.com

從上圖中,可以觀察到 Xorg 在系統更新前的舊版本,其使用的 mem 都不到 200MB , 而且運作 2~3 個星期以上,都沒有任何 mem 用量累積。 zero memory leak 水準的軟體確實存在,是真的可以達成的,而且就在我每天都在使用的軟體之中。或許,更高水準的軟體方法,不見得在最新的技術潮流,而是隱身在這些老牌軟體專案的某個角落,等待著我們再重新去發掘它。

註1:
FB 上的留言討論
https://www.facebook.com/groups/hackingday/permalink/903494983005834/

註2:
我所使用的觀測工具是 shell script + collectd + graphite/influxdb
我寫的 scripts 大致如下:

View post on imgur.com

View post on imgur.com

View post on imgur.com

View post on imgur.com

View post on imgur.com