[工作日誌] #5 若問題類型是極端狀況,那是寶藏



極端狀況的認知


有時候狀況不可預期,無法預期就無法評估或控制。

極端狀況所造成的災難,很明顯與直接地造成公司或部門的損失。

災難範圍多有大,取決於看運氣,可能引發ㄧ點點小部分的錯誤或異常。

有可能引起一連串的反應,拉著一卡車的相關部份一起陪葬。

兩者我們都有遇過。

我所謂的極端狀況,指的是,超出系統架構設計上能承受的負荷。

系統設計和安排上,總有些前提,和預期的控制範圍,若失控崩潰絕對不會有好下場的。



危機、轉機,發現價值


這些處理和理解極端狀況的部分,撇開公司的受損另外討論,對技術單位根本是寶藏。

技術單位的技術儲藏、看家本領的技術底蘊,不是從這裡來,要不從哪來?

自己的架構、設計、環境、業務處理邏輯,從外面哪能遇到的這麼貼切的類似情境,

外面哪裡有可參考的極端狀況,成因和處理可以學習?

這種成因,分析方式,試驗手法,以後要避開的設計誤區,考量狀況重點,最應該寫成文件或範例,並且在部門交流內讓每位開發者深刻認識與學習。

不然災難過後所留下的ㄧ點好處,只有能夠理解的人獲得殘餘價值,然後在其他人面前大剌剌的漂過,然後呢?

我的媽呀,同一個部門大家做的業務性質處理很相近,其他人改天再弄一次嗎?

付出群體的代價,卻沒有群體的收益,倘若技術只綁在少數人,如果人走了呢?技術部門是不是某種程度上又打回原型。


比喻


接下來我就舉兩個例子,引發災難ㄧ小一大,兩份珍貴的收藏。

剛好都是與資料庫相關的case。

看不懂的人可能覺得沒價值,跟一般的小錯誤沒什麼兩樣,但是請聽我說來,這跟一般的小錯誤有什麼不同。

很多的小錯誤,業務或邏輯,或者粗心所製造的錯誤,有些方法可以防範,邏輯思考模式經過長期訓練,也可以趨於完善,可以檢討SOP,或用測試手法預防。

但這樣能解決的問題,還叫做遇到極端狀況嗎?

想得到,測得到,預防的到就不叫極端狀況了,所以這種經驗你說可不可貴。

遇過了,之後可能就不一樣。

打個比方,你要防衛敵對國家的武力進犯,你的評估是對方武裝實力其實跟我們差不多‧

你做了海上防禦部屬,陸上陣地部屬,空中預警部屬,甚至綜合打擊部屬,因為我們要打人家也差不多是這樣的方向。

結果開戰當下,敵人竟然使用太空打擊武器!

如果僥倖活下來不死,是不是應該警惕地審慎思考過所有環節?首先你自家的情報單位就該死了吧?


案例一事發主因:


「資料庫主從同步落後」

狼來了


媽勒,部門的人得知時的反應,感覺在排演聽到狼來了,反應類似「又主從落後啦」「以前發生過」「客服部門沒有相關客訴進來」「再跟主管通知吧」「之前有聽說他們只保證5秒內同步到」。

處理問題的當事人不是我,但我在旁傻眼到不行。

我不禁想到一個很好笑的畫面,狼真的來了,光顧好幾次了,我們一群羊知道後,在聊天說「又來啦」「問題不大,現在知道只有一兩隻羊被吃掉」「之前有反應過有狼呀」「我們當羊的又不能怎麼樣」。

案例操作情境與分析


情境是這樣的,我們的系統有網頁的服務,在使用者按下執行的按鈕,API收到request之後到DB讀取,檢查這個控制執行的狀態,若狀態已經是「已執行」,則這個request無效,返回錯誤訊息。

檢查到若是「未執行」,則將狀態改為「已執行」寫入DB後,做後續動作。

如果相同資料ID的request連續進來,檢查時都是看到「未執行」的狀態,當然就執行多次將狀態改為「已執行」的語法寫入,然後執行多次的後續流程,但你預期應該只有一次。

很家常的設計,跟吃飯一樣簡單自然的設計,這樣能遇到極端狀況,我也是心情蠻複雜的。

問題在於,我們的資料庫採取「讀寫分離」,寫入是用master DB,讀取是用slave DB。

將狀態改為「已執行」,成功在master寫入了,程式還執行完後續的流程,response還回到使用者的瀏覽器了。

「主從同步」還沒好......,從slave撈出的狀態還在「未執行」。

也許你會想說,前端在知道按鈕按下去,按過一次就不該讓他按了,它怎麼會有機會按第2次以上?

不好意思,如果使用者事先重複開出很多分頁的話,切換分頁一點再點......我也不知道用意是什麼。

也許你會想說,可以看update 時候的row affect數量做為檢查的方法。

沒錯,當初只用update時候有沒有收到error來判斷要不要繼續往下做,現在看來的確有些不足。

但若假設其他專案,或其它地方,靠update 和select同樣欄位做狀態判斷,寫入和讀取的操作並非在同一隻API處理呢?

單單用select決定後續流程,那可能要先心知肚明,這些流程如果重複跑是沒有關係的,不可限制同樣的資料ID只跑一次,

你能確定每個開發者做那部分的設計時,都能想到這樣嗎?

後來,我們決定將這個取狀態的DB連線,改用master的連線,就這樣結束這回合。


屬於我的後續


事情就這樣完了嗎?出錯的資料只有一小塊,修正好,連線改成master,不會再發生同樣的狀況就好了,

我們這邊主管有通知資料庫部門,他們也給了回覆,「以後有主從落後的情況會恢復主動通知喔」,事件和平落幕。

媽勒,我覺得除了我感到沒完沒了,為什麼大家都覺得事件和平落幕。

對,就我腦袋有問題,腦袋有問題的羊,是不是應該離開這群腦袋正常的羊,太給大家困擾了。


能夠安心睡覺的條件不見了,你們都不怕?


首先,這次的主從落後事件,跟以前的主從落後事件,情況和影響真的一樣嗎?有人想過這件事情嗎?

很多人不會對問題謙卑,不願花太多心力思考那麼多,只憑過去經驗快速的判斷新遇到的狀況。

你確定現在是以前沒解決的那匹狼又來?還是來的是狼群?還是其他猛獸?

事發時才赫然發現,好久沒有收到資料庫部門主從落後通知了,為什麼不通知了?

有主從落後的情況發生多久了?

這陣子以來發生了幾次落後?落後多久?1秒和100秒可能造成災難的規模是完全不同的?好像有聽說有100多秒情形已經有一陣子。

我們有哪些系統功能,主從落後可能會受什麼影響,範圍呢?日期區間呢?我們要怎麼檢查,要檢查什麼?

各位不想這些問題,憑各位的經驗,沒客訴過來就沒問題了,是我太緊張太神經質。

反正呀,現在人家資料庫部門也說會繼續主動通知啦,以前會示警的看門狗有天不見了,狗為什麼不見不要緊,羊有沒有丟不用點,都不要緊,現在又有新的狗囉。

對,我們管不到狗,我們自己也無法點羊,現實狀況如果很簡單,假設總數只有3頭羊,隨便少了一隻誰都看得出來,我們的難處就是太複雜太困難,但就放棄掙扎不試著反抗?


對過去經驗檢討


換個層面討論,系統程式設計部分,拿今日的教訓來考量,開發新的系統功能的時候會怎麼想?

整個團隊該怎麼看待這件事情?

主從一定要做,我們系統業務量大,讀寫不分離資料庫無法負荷,但經驗下來,我們環境有的時候,就是不能保證主從可以即時同步。

到這裡,我就會想到部門內好像有人告訴過我,讀寫就是要分離,讀就是要遵守只用slave的規範。

現在來看,這個經驗傳承可能要修正,寫入的語法可預期不會花太多效能,又需要即時確保狀態值的select是不是可以用master連線?

我不知道我們開發部門知不知道這個想法,或者我想法會不會有其他問題?

我現在寫的程式語言,DB的連線套件要隨時指定連接master或者slave,由開發者決定。

但是承過往經驗(大家到底會不會想過去的東西?相關狀況或處理經驗作連結呀?)

我們在使用laravel的時候,它這部分的設定蠻厲害的,設定檔設定好,他會自己判斷你是寫入還是讀取,自動採用master或slave,如果讀取要用master,需要特別帶參數指定。

如果今天是一個菜鳥,或者一個laravel的入門使用者不知道這個部分,那麼他的專案不就很老實的寫就是鐵定用master,讀就是用slave。

如果他開發的是像我們案例上的那樣設計呢?點按鈕,確認狀態,改狀態,完全沒機會遇到主從不同步導致的災難嗎?

然後我們DB就是不能保證主從即時同步。

如果今天是個有些經驗的工程師,強制在DB連線的底層套件,或者redis的連線套件,幫你包裝作判斷,寫入就是強制連master,讀取就是用slave,那會不會引起慘案呢?

不要覺得不可能,因為強制連線的作法我就有考慮過這麼幹,開出來的原則和設計鐵則就應該遵守,有的人就是天馬行空,自己想怎麼寫就怎麼寫造成問題,如果有辦法規範這種部分,我可是非常樂意。

但不好意思,現在資料庫無法保證即時主從同步。

我到底要不要擔心過去的舊專案,讀寫分離做得很徹底,可能主從落後已造成事故?


有人在乎事實真相?


事發當下,造成主從落後的詳細原因,我不知道有幾個人知道得很detail。

詳細原因是,出事的系統使用的DB,跟其他專案用DB,設定上是同一個機群。

即使兩個系統的DB所在實體機器不同,但不好意思喔,設定上在同一個機群,所以兩個DB連線出去的主從同步會互卡,

這就是問題原因,很專業,也是資料庫部門更之前的告知,才能知道這知識。

當時出事的系統DB,就是被其他系統的DB給拖累的。

這個知識重點我不確定到底幾個人知道,考慮未來新的系統環境建置,新的資料庫建置,評估上是不是還能跟其他系統放在同一個機群?

這種經驗儲備不是團隊內,每個人都能清楚認識,我覺得非常可惜,

所以專注培養精英戰力嗎?以後開發設計建DB都交給專業負責人員?不可能吧。

後來說,出事的專案DB會跟造成拖累的DB,安排擇日切離同一個機群喔。

好棒棒,覺得這樣子以後就不會同步落後嗎?據我所知,我們專案有部分的語法有效能問題,自己造成同步落後,自己雷自己。

只是恰巧好像沒影響到,雖然後續已經有再做處理,調整那部分的問題。

但整件事情,我看到的背後是我們作業環境,根本沒有事先、或者即時能夠評估主從落後的方法,SOP不健全,

資料庫部門沒辦法,等出事故才能知道。

不能保證DB主從可以即時同步,未來的各種系統設計變相要承擔這種責任,考慮上要處處注意。

我很難容忍這種事情,有職務負責的話,我很願意跳下來試圖處理、試圖改善。

案例二事發主因:


說明


「mysql prepare statement 上限65535」

炸出來之後一發不可收拾,甚至還有數度餘震。

怎麼會串語法條件和內容串到65535個呢?我事先也沒有預期到可以串超過65535個資料,然後就爆炸了。

平常要遇到能串65535個資料的機會也沒有,史無前例。

為什麼要串那麼多東西呢?因為我們業務量大,追求快,追求迅速,資料核對上又不能有錯,最終方案借由DB有中index的方式,

計算後得出的方案與採取作法,結果還蠻成功的,1萬、2萬的資料一次連線就做完,而且速度很快,幾個月來也穩定安全。

誰知道類似做法,在下個專案會處理出現超過65535的情況。

你要問為什麼沒有先測過嗎?大量測試?

不好意思喔,我們沒有自動化測試的環境和條件,我明示暗示了許多遍,默默都不見。

我有方向怎麼做,很困難,花很多成本,甚至要別部門配合,無法配合的話,自己就要有更辛苦的心理準備,和更高的成本評估。

but沒人有興趣知道呀,的確我沒實踐做過啦,但每個聽到我開頭提測試,都好像聽到「安安,請問你聽過安麗嗎?」

回頭就閃了,叫我怎麼介紹產品,根本就沒興趣呀。

帶來的經驗


話說回來,這個案例,大家會思考出什麼結論?來講講我的。

1. 理論上,ㄧ次連線塞了非常龐大的語法,是可行的,網路封包會自己切,DB會自己處理好,只是理論歸理論,沒有有資料或者做實驗,又或者就是會怕,沒人願意這樣做看看,現在可以背書不用怕什麼了吧?

2. 事發的情境是用select,where in (?) 裡面的條件串超過65535,有人推測到其它專案會不會有類似隱憂,這點我覺得很值得稱讚。

3. 除了select,推論了一下,也可以認識到bulk insert的數量真相。

記得很久以前,跟前資料庫部門人員討論bulk insert到底一次塞幾筆資料比較安全?

那時後測試了1000筆就切割成一次的insert,對方說有做過2000的沒問題。

好喔,現在就知道那時的假設我們缺了關鍵點,重點應該是每筆insert,你的DB有幾個欄位要塞?

若一筆要塞10個欄位,那大概上限可預期在6500筆,若100個欄位的話,就大概650筆一次是極限。

想要快,要好,就要用數字佐證,知道極限在哪,最大化使用每個部位,每拿到一種新的數據都是新的力量來源。

很可惜好像喜歡討論這種根據和數字的人不是很多。

狂言狂語


上面不過是我經驗心得的兩則,日復一日,年復一年,經驗累積下來,我自己有強烈的信心每次程式設計會更強大,

憑著知道要思考這些東西,連結過去經驗,窮究現在狀況,思考未來如何處置。

工作十年,十年經驗,你要怎麼知道你真的是這樣子,還是工作十年,一年經驗重複用十年呢?

不要放過珍貴的問題在眼皮底下,一個一個溜過去,當然如果你的想法是專注處理好「目前的問題」,那就算了吧。

留言

這個網誌中的熱門文章

[Go] 型態轉換 type convert

[Go] Golang用法 package import 前面的底線

[Go] 指標 pointer with golang