最近的生活真的是忙到焦頭爛額,一邊在水深火熱地幫論文補數據,另一邊的實務合作專案也遇到了一個非常真實、且極度折磨人的工程挑戰。
這篇文章不談太具體的商業邏輯,單純來聊聊我們在系統架構上踩到的一個巨大效能坑,以及一個工程師在 Debug 卡關時,到底會做出什麼荒謬的紓壓行為。
平行運算的魔法失效了?
這個專案的底層架構,是一套由 Python 負責前端策略處理、交給 Java 核心引擎進行高速運算的系統。
原本的運作模式是非常安分守己的「序列化」。也就是說,系統必須等第一天的巨量資料跑完好幾分鐘,才能接著跑下一天。看著伺服器那精美的多核心 CPU 閒置在那邊,身為一個對效能有追求的工程師,我當然不能忍,於是決定發揮平行化程式設計的技巧來榨乾硬體效能。
結果,意外發生了。我原以為加上 Parallel 就像施了魔法一樣會自動起飛,但我剛查了 Log 卻發現,即使我非常保守地只開了 2 個 Worker,平行運算依然華麗地失敗、整個系統直接崩潰斷線。(我先嘗試6然後4然後2 都失敗)
傲嬌的 Java 引擎與慢郎中 Python
看著滿螢幕的錯誤訊息,我一開始的判斷是:「這 Java 核心引擎也太傲嬌了吧!」
錯誤代碼顯示的是 SocketTimeoutException。我當下的推論是:這套 Java 引擎對於「通訊延遲」有著極其嚴格的限制。當伺服器同時跑兩套 Java 引擎和 Python 腳本處理全日版的龐大資料時,瞬間的 CPU 爭奪造成了延遲。Python 無法在規定的「微秒級」時限內把成千上萬筆的資料傳過去,所以 Java 引擎就直接無情地把連線切斷了。
聽起來很合理對吧?但身為一個對技術抱持懷疑態度的人,我總覺得哪裡怪怪的。底層的資源競爭真的會讓通訊慢到觸發 Timeout 嗎?
於是我繼續往下深挖,把平行版斷線的 task.log 徹底翻了一遍。結果迎來了史詩級的劇情反轉:
Java 引擎是無辜的,真正的戰犯,其實是 Python!
史詩級的 JSON 效能慘案與修復
真相大白。SocketTimeoutException 雖然是 Java 拋出的,但根本原因在於:Python 內建的 json.loads() 處理速度實在太慢了。
在資料的巔峰時段(例如某些流量爆衝的極端日子),每 5 分鐘就會有一波極其龐大的狀態 JSON 湧入系統。當 6 顆 Java 引擎同時把海量 JSON 砸向 Python 時,Python 原生的解析器根本來不及消化。這導致 Socket 直接塞車堵死,最終 Java 引擎在對面苦苦等不到回應,只好無奈地強制切斷連線。
找到 Root Cause 後,我立刻進行了一場全局的史詩級加速修復。
我強制介入,把系統內所有的 JSON 解析工具,全部換成了底層用 C/Rust 改寫的極速版函式庫 orjson(它的解析速度直接輾壓原生 Python 10 到 20 倍以上)。我深入修改了通訊伺服器的原始碼,把所有的跨語言通訊橋樑全部換成 orjson 驅動。
改完之後,系統終於不再 Timeout,資料流順滑無比,平行運算也正式起飛。事實證明,在 real-world engineering 裡面,沒有什麼「一鍵加速」的魔法,所有的效能瓶頸都藏在最不起眼的 I/O 和底層解析裡。
Debug 崩潰後的行為藝術:LinkedIn 彩蛋計畫
好,技術故事說完了,來說說我在找出 orjson 這個救星之前,處於卡關煩躁期做了什麼事。
人在解題遇到瓶頸的時候,通常會做一些毫無關聯的事情來逃避現實。於是我一怒之下,打開了 LinkedIn,花了幾個小時把我的完整履歷跟經歷徹底翻新整理了一遍。
既然要翻新,我就不想放那種冷冰冰、假大空的罐頭文字。為了紀念這些苦中作樂的大學與碩士生活,我順便在檔案的深處(特別是教育背景裡),埋了非常多真實又荒謬的「小彩蛋」。
如果你滑到我的 LinkedIn,在國立臺灣大學的學歷底下看到了一個神祕的 +16 照片堆,千萬不要懷疑,點進去就對了。裡面沒有無聊的獎狀,只有像是「打賭輸了在路邊發 20 個便當」、「代替我拍畢業照的水煮蛋 Jellycat」,以及各種研究生與 GPU 的愛恨情仇。
如果你剛好路過我的主頁,願意點開來看看,並且成功發現了這些彩蛋,歡迎截圖或私訊跟我分享!作為解謎獎勵,之後我可以去 Google 辦公室拿一些好吃的零食來犒賞你 🍪☕️
祝大家的平行運算都不會 Timeout,JSON 都能秒解。我們下一篇文章見!