「鍾馗抓鬼系列」靈光一閃,拔除肉中刺!


        長久以來,我幫客戶開發的某系統一直有個奇怪難解的問題 - [ 資料重複 ]。這個問題在測試機上從未發生,在正式機短則一兩個月才發生一次,但是因為資料牽扯到帳務會影響客戶金額計算,所以只要偶發一下,就會搞得大家人仰馬翻,檢討會議開不完。

        這種偶發的錯誤,是程式開發人員的噩夢 - [ 盯著他看,偏偏都正常。一轉身離開,就給你胡搞出亂子 ]。



        這個錯誤只會在每次剛啟動程式時的第一筆資料發生,之後就平步青雲。但也不是每次啟動都會發生,那支程式每天都會做一次重新啟動,但有時兩三個月平靜無波,這就是難搞的地方。自己 Review 檢視程式碼不下數十次,都完全找不出邏輯的問題,不得已,上次發生資料重複時,最後只好猜測是我們引用的第三方程式庫內部隱藏的臭蟲,然後冒險將第三方程式庫更新到最新版,期望瞎貓碰到死耗子,真的被我掩耳盜鈴地誤打誤撞解決。

        就這樣平靜兩三個月,正在想或許真的是第三方程式的問題,以為真的從此根除此難纏的問題,然而同樣的問題上週又發生了!

        我有種孤臣無力可回天,技窮只能引咎辭職。基於此問題長期未解,客戶堅持我一定要到場調查解決,但到了現場有如何,這個問題又不是想發生就發生,不知道原因就是不知道原因,我最怕這樣大眼瞪小眼,面面相覷的場面。是的,辭職的最佳時機點到了,逃避人生無力解決的問題的終極辦法 - 自殺,逃避工作無力解決的問題的終極辦法 - 辭職。此時不遞辭呈更待何時?

        寫到這,這篇文章應該被我歸類在工作甘苦談的,但就在我硬著頭皮進入客戶機房打算進行大眼瞪小眼的長期抗戰時,突然間靈光一閃,竟然被我找出此懸案真正的兇手,解決此一問題!

        基於正向思考原則,這篇文章改置於 [ 程式設計 ] 底下,作為切身之痛的一個經驗分享,想當然,寫辭呈的執行力又再度石沉大海。

        首先來看看我 Review 無數次的程式邏輯圖吧:

        忽略所有系統面的錯誤猜忌 ( 我有做許多系統面的 Error Handling ) ,單就邏輯面來談。留暫存檔的理由,是避免資料庫寫入失敗 ( 網路不通,遠端資料庫 Lock...etc ),或是系統異常瞬間斷電,讓主機的資料蒸發於無形,所以收到資料第一措施就是寫成實體的暫存檔。正常情況下,只要確認資料庫寫入成功,程式就會把暫存檔刪除或是移動到他處。所以程式一開始啟動會先檢查暫存目錄內是否有遺留的暫存檔,若有遺留,必定是上次系統異常關機或是作業人員停止程式所遺留的,在不確定這些資料是否成功寫入資料庫內,所以必須交給另一個專門負責 Retry 的另一支程式。

        負責 Retry 的程式為了避免資料重複,會以 MD5 Hash 編成的序號來檢查是否之前已經有同樣的資料,若無就嘗試寫入。

        對我來說,整個流程完美無缺,怎樣都不可能產生重複的資料!若說有遺失資料倒還可能 ( 可能收到主機資料到寫入成暫存檔之間那個瞬間機器停電,程式異常中止 ),但是重複...?怎麼想都看不出事情怎麼發生的!

        OK,當時我檢查 log,發現這筆資料被負責 Retry 的那支程式成功寫入資料庫過一次,而原本的程式也寫入過,因為 Rery 的程式會檢查 MD5 Hash 有沒有重複,所以他若成功寫入,代表事情發生的經過是 Retry 先寫入資料庫,之後原本的程式才緊接著寫入。所以現在的疑點是,為何原本的程式還沒嘗試寫資料庫之前,Retry 的程式就可以從失敗資料夾內拿到該暫存檔呢?

        謎啊!真是個謎啊!

        我當場愣了數分鐘,突然想通一切是為何發生了!

        因為客戶對原本的程式執行了三個,只是吃不同參數檔的實體,這些來自於同一支程式的數個執行實體,設定使用的暫存資料夾其實是同一個資料夾!一切都真相大白,瞭解了嗎?

        既使是同一支程式的不同執行體,但啟動的時機點不會完全相同,當第一個實體自然會清空所有暫存資料夾內的暫存檔,移動到失敗資料夾中,接下來他開始從主機接收資料,在暫存資料夾內產生新的暫存檔,這時候比它晚一點 ( 可能晚半秒或是幾秒而已 ) 啟動的第二個或是第三個執行體,一樣會進行清空暫存檔全部搬到失敗資料夾的動作,這時候剛才第一個執行體接收主機資料而產生的暫存檔,被它們錯當上次程式結束遺留下來的暫存檔,而全數搬到失敗資料夾內做 Retry,只要 Retry 那支程式速度夠快,讓這一切發生在第一支程式產生暫存檔和寫入資料庫之間那電光石火的瞬間空檔,Retry 程式搶先寫入資料庫,那資料重複的錯誤就會發生!我驗證推論的證據,就是其後在事件檢視器 EventLog 內找到第一支程式成功寫入資料庫後,企圖把暫存檔搬移所發生的錯誤訊息:找不到檔案。


        這樣的天做姻緣是需要多麼微小的機率去成就啊!?怪不得要幾個月才發生一次...

        解決辦法,設定每個執行體的參數檔,使用的暫存資料夾個自獨立,互不干涉。相信這之後我可以長居久安,不用再為了這個困擾我長達半年還是一年的夢靨煩憂了,就讓這個錯誤化作歷史遺跡,塵歸塵,土歸土吧。

熱門文章