這篇文章適合搜尋引擎工程師、後端架構師以及正在處理大規模向量檢索(Vector Search)團隊閱讀,深入解析 Uber 如何解決 15 億級別數據的寫入瓶頸與查詢延遲問題。
三個精華點:
-
技術選型轉向 OpenSearch:Uber 原本使用 Apache Lucene,但受限於算法單一與缺乏原生 GPU 支援,無法滿足日益複雜的語意搜尋需求。轉向 OpenSearch 後,利用其支援多種 ANN(近似最近鄰)算法的靈活性與未來對 FAISS (GPU 加速) 的整合潛力,解決了擴展性問題。
-
寫入效能優化(提速 79%):面對 15 億筆向量數據,預設設定導致索引建立需耗時 12.5 小時。團隊透過「增加 Spark 並發數」、「關閉預設的每秒 Refresh (改為 -1)」、「優化 Merge 與 Flush 策略」以及「移除
_source和doc_values縮減索引大小」,將時間大幅縮短至 2.5 小時。 -
查詢延遲減半與藍綠部署:為了達到 P99 < 120ms 的目標,Uber 發現「Shard 數量等於節點數量」時效能最佳,並透過增加 Replica 來平滑延遲。此外,為了解決建索引時搶占資源的問題,他們採用了藍綠部署(Blue/Green Deployment),在獨立集群建立索引後再切換流量,確保線上服務零停機且不掉速。
這篇文章提到的一個核心痛點:「預設值永遠只適合 Hello World,不適合 Production」。
在處理像 Elasticsearch 或 OpenSearch 這類搜尋引擎時,許多人都有過類似的慘痛經驗:在資料量小的時候一切完美,但當數據量上億時,預設的 refresh_interval (1秒) 和激進的 segment merge 策略往往會導致磁碟 I/O 爆炸,甚至讓整個 Cluster 卡死(Stop-the-world)。
Uber 這裡展示的優化手段非常教科書級且實用,特別是他們對於 I/O Amplification(I/O 放大)的觀察,以及果斷放棄 _source 欄位來換取效能的取捨(Trade-off)。此外,他們提到的「藍綠部署」策略雖然聽起來是運維層面的事,但對於搜尋業務來說至關重要——永遠不要讓繁重的寫入任務(Indexing)去拖垮使用者的查詢體驗(Querying)。這篇文章是從「堪用」到「高性能」的最佳實踐範本。
為了像我一樣,一開始對技術名詞一片空白的朋友,而準備的「白話文」技術名詞解釋。這些詞彙雖然聽起來很艱深,但其背後的邏輯其實跟日常生活非常貼近。
1. ANN (Approximate Nearest Neighbor)
- 白話翻譯: 「差不多準確」的快速搜尋法。
- 情境解釋: 想像你在一個巨大的圖書館找一本「關於太空冒險」的書。
- 傳統精確搜尋 (KNN): 你必須把圖書館裡幾百萬本書每一本都拿起來看,確保找到那本「最最最」符合的書。這非常精準,但可能要花好幾年(電腦會算太久)。
- ANN: 你直接問圖書管理員:「科幻小說區在哪?」然後直接去那個架子上挑一本最順眼的。雖然你可能錯過了一本躲在角落的絕世好書,但你只花了 1 分鐘就找到了「足夠好」的書。
- 重點: 在大數據時代,為了「快」,我們願意犧牲一點點「精準度」。
2. FAISS
- 白話翻譯: Facebook 開發的「超級找書機器人」。
- 情境解釋: 如果 ANN 是「找書的方法」,那 FAISS 就是 Facebook (Meta) 寫好的一套超強工具庫(Library)。它就像是一個受過特種訓練的圖書管理員,動作極快,而且還懂得用 GPU(顯示卡)來加速。大家不用從頭寫程式去實作 ANN,直接用 FAISS 就好了。
3. Spark 並發數 (Concurrency)
- 白話翻譯: 同一時間有多少個工人在搬磚。
- 情境解釋: Spark 是一個處理大數據的工具。
- 低並發: 只有 1 個工人在搬 1000 箱貨物(這個工人已沒有靈魂)。
- 高並發: 老闆良心發現,請了 100 個工人同時搬貨,效率瞬間提升 100 倍。
- Uber 做的優化: 就是「多請一些工人」,讓電腦同時開更多條線程(Threads)來處理資料寫入。
4. Refresh (改為 -1)
- 白話翻譯: 暫停「即時更新」,等貨都補滿了再開門。
- 情境解釋: 搜尋引擎(如 OpenSearch)預設每 1 秒會「Refresh」一次,就像便利商店店員每 1 秒就跑去整理架上的貨,讓你隨時都能買到剛上架的東西。
- OpenSearch 的運作機制: 貨車(寫入數據)把貨卸在倉庫(記憶體)時,客人(搜尋者)在賣場是看不到這些貨的。必須經過一個叫
Refresh的動作,店員才會把貨從倉庫搬到賣場架上,這時客人才搜得到。 - 預設值 (每 1 秒 Refresh): 就像貨車每卸下一罐可樂,店員就立刻暫停卸貨,拿著這一罐跑去賣場上架。
- 壞處: 店員一直在跑來跑去(消耗 CPU),導致貨車卸貨很慢。
- 優化後 (Refresh = -1): 店長下令:「現在開始,不要把貨拿到賣場!先把鐵門拉下來,專心把十億罐可樂全部卸貨堆在倉庫裡。等全部卸完了,我們再一次全部上架。」
- 好處: 少了來回跑動的時間,卸貨(建立索引)的速度就飛快了。
- OpenSearch 的運作機制: 貨車(寫入數據)把貨卸在倉庫(記憶體)時,客人(搜尋者)在賣場是看不到這些貨的。必須經過一個叫
5. 優化 Merge 與 Flush 策略
- 白話翻譯: 優化「打掃」與「整理」檔案的頻率。
- 情境解釋:
- Flush (沖刷): 就像你寫小抄,要影印給要被二一的同學,而寫在便條紙上(記憶體),那Flush 就是影印好的紙(硬碟)。就像寫一個字就跑去影印機印一次,影印機(磁碟 I/O)絕對會過熱罷工。優化後變成「寫滿一張紙再影印」,減少動作次數。
- Merge (合併): 小抄上有很多零散的頁面(Segment),Merge 就是把這些散亂的頁面重新謄寫成一本厚實的書。Uber 調整策略就是告訴系統:「現在先別急著整理書(合併),先把內容抄完再說」。因為「整理書」這個動作非常花腦力(CPU),如果在趕著寫小抄的時候還要一邊整理書,速度一定變慢。
6. Segment Merge 策略 (補充說明)
- 白話翻譯: 把很多小箱子打包成大箱子的規則。
- 情境解釋: 假如你要查「去年在 7-11 花了多少錢?」
- 合併前(許多 Small Segments): 你的抽屜裡塞滿了 365 張皺巴巴的發票(就像 365 個小檔案)。
- 搜尋過程: 你必須把這 365 張發票一張一張攤開來看,一張一張加總。
- 缺點: 動作很慢,而且翻找的過程很吵(磁碟讀取次數多)。
- 合併後(Merged Segment): 系統在背景很貼心地幫你把這 365 張發票的內容,整理並抄寫到 1 本年度記帳本 上(這就是合併成 1 個大檔案)。
- 搜尋過程: 你只需要打開這 1 本 記帳本,一眼就看到總金額。
- 效益(Benefit):
- 搜尋速度變超快: 從「翻 365 次並加總」變成「翻 1 次得到關鍵數字」。
- 省空間: 丟掉那些皺巴巴的發票紙張(刪除重複或過期的標記),只留乾淨的紀錄。
- 為什麼要優化? 因為 Uber 現在正忙著把十億張發票塞進抽屜,如果這時候還要分心去「整理記帳本」,會佔用大量的 CPU 和磁碟讀寫,手會打結,導致卡頓。所以 Uber 的策略是:**寫入時先別太頻繁合併(先亂塞沒關係),等寫完了再慢慢整理。
7. P99 < 120ms 的目標
- 白話翻譯: 保證 99% 的人都覺得「很快」(0.12 秒內)。
- 情境解釋:
- 平均值 (Average) 的陷阱: 如果馬斯克走進平民窟,大家的「平均資產」都會破億,但這不代表大家都有錢。
- P99 (第 99 百分位數): 我們把 100 個使用者的查詢速度從快排到慢。第 99 個人的速度代表了「絕大多數人的體驗」。
- 含義: 只要 P99 < 120ms,代表 100 個人裡面,有 99 個人的搜尋結果都在 0.12 秒內跑出來。我們允許那第 100 個人(可能是網路爛)慢一點,但我們要保證絕大多數人的體驗是非常絲滑的。
8. Blue/Green Deployment (藍綠部署)
- 白話翻譯: 蓋兩間一模一樣的店,裝修好新店再把客人導過去。
- 情境解釋: 你要升級系統或重建索引(Index)。
- 傳統做法: 讓店裡一邊施工一邊營業,客人會覺得很吵、塵土飛揚(系統變慢、報錯)。
- 藍綠部署:
- Blue (舊環境): 目前正在營業的店,客人繼續用。
- Green (新環境): 在旁邊蓋一間全新的店,關門偷偷把幾十億筆資料灌進去。
- 切換: 等 Green 準備好了,只要把門口的指路牌(Load Balancer)一轉,所有客人瞬間引導到新店。舊店就可以關掉或留著備用。過程完全無感。
9. 什麼是「預設值永遠只適合 Hello World,不適合 Production」
- 白話翻譯: 原廠設定是給新手練習用的,不是給職業選手比賽用的。
- 情境解釋:
- Hello World: 程式設計的第一課,代表「最小規模的測試」。
- Production: 真實戰場,像 Uber 這種幾億人在用的系統。
- 比喻: 你買了一台電腦,預設是「省電模式」。這對只拿來打字的人(Hello World)很好。但如果你要拿來跑 3D 遊戲大作(Production),你不把省電模式關掉、不把風扇開到最強,電腦一定會過熱當機。軟體的預設值通常是為了「方便、省資源、不出錯」,而不是為了「極致效能」。