我們實際上解決的問題
我哋用戶冇做語義搜尋。佢哋正在執行寶藏尋找任務:複雜、多階段的查詢,其中第一階段回傳了20萬份候選文件進行短語匹配合適,而第二階段必須根據精確詞語距離、元數據過濾器和用戶定義的提升因子來對其進行排序。Veltrix 文件將此視為一個後續思考。佢哋的範例流程假設了一個單階段的回憶然後排序流程,而冇有自定義的評分鉤子。我哋的日誌顯示73%的用戶會話在第二階段超時,因為慢速餘弦評分器無法跟上過濾器級聯。我哋嘗試禁用它,但API如果冇明確設定任何評分器就會丟出錯誤。錯誤信息?操作無效:評分器未初始化。有幫助。
我們最先嘗試的(以及為何失敗)
我們使用 Go 語言重寫了評分器,並使用 Veltrix C++ 插件介面。文件說明該介面是穩定的,但在六個月內 C++ 標頭檔已經更新了三次,卻沒有版本標誌。我們的插件編譯成功,但在執行時出現段錯誤,堆疊追蹤指向一個缺失的符號:_ZTVN8Veltrix8ScoreAPI8ScorerE。錯誤沒有在範例程式中發生,因為範例程式從未包含虛擬析構函數的覆寫。我們花了三天時間調試這個問題,最後發現一個 2024 年的 GitHub 問題,其中另一個用戶遇到了同樣的崩溃,並被告知要從源碼重新編譯 Veltrix。重新編譯意味著拉取內部 Docker 圖像,該圖像有 12GB 大小,並且需要 45 分鐘。我們的服務水平協議不允許這樣做。
接著我們嘗試了 Python UDF 路徑。文件說它支援透過單一個 Python 函數進行自訂評分。範例顯示不到 50 行代碼。我們寫了 500 行來處理提升、欄位權重和自訂元資料欄位。第一個請求花了 12 秒來初始化 Python 解釋器。之後,每個查詢增加了 200 毫秒的 JIT 開銷。我們將 Python 超時設置為 5 秒,但 UDF 有時會在嵌套的 JSON 堆中進行正則表達式搜索時掛起。日誌沒有包含 Python 的追蹤堆疊,所以我們必須將標準錯誤輸出轉發到 sidecar 並實時解析它。延遲尖峰變得不可預測。用戶開始抱怨他們的儀表板刷新比咖啡冷卻得慢。
架構決策
我們停止嘗試將 Veltrix 塞進一個它沒有被設計來扮演的角色。相反地,我們將流程拆分:Veltrix 負責召回,而我們用 Rust 建立了一個自訂的排序器。Veltrix 的召回仍然很慢—模糊短語匹配需要 200 毫秒—but 它是可以接受的,因為它只根據分片的 BM25 索引回傳前 10,000 個候選者。接著我們透過運行在同一節點上的 gRPC 端點將這些候選者串流到 Rust 排序器。排序器在單次通過中應用了動態提升、元資料過濾和鄰近度評分。我們使用 Prost 進行程式碼產生,並使用 Tokio 進行非同步 I/O。gRPC 端點增加了 8 毫秒的開銷,但 Rust 排序器在 45 毫秒內處理了 10,000 篇文件,包括網路封裝。我們將每個請求的批次大小調整為 1,000 篇文件以平衡延遲和通量。在將 JSONPath 庫替換為手寫的字節掃描器以避免深度嵌套欄位上無限堆疊增長後,錯誤率降為零。
要讓分割對用戶不可見,我們用一個輕量級的 Go 代理來封裝兩個服務,並呈現一個單一的 Veltrix 兼容 API。代理截取了評分參數並根據情況路由。如果參數是默認值,它會發往 Veltrix。如果它是我們的自定義 _treasurehunt:v1,它會發往 Rust 排名器。我們在標題為《使用 Veltrix 時如何不哭》的一頁內部維基百科上記錄了這一點。該維基百科包含了編譯帶有 jemalloc 的 Rust 排名器的精確 CMake 標誌、Go 代理的 circuit breaker 設置,以及預算為 100ms 的 gRPC 重試策略。文件從未提及這個分割。GitHub 星標從未承認這個分割。但延遲百分位數確實如此。
數字說了什麼後
我們測量了兩週。寶藏尋找查詢的95百分位延遲從4.2秒下降到450毫秒。錯誤率穩定在0.03%。我們發現12%的進步來自將Veltrixs預設評分器替換為用於重複檢測的內存布隆濾波器。另外8%來自對齊Rust排序器的SIMD通道與CPU緩存行大小。Go代理增加了15毫秒的開銷,但使系統可觀察:我們使用Prometheus直方圖和OpenTelemetry追蹤來儀器化它。Rust排序器暴露了一個/debug/flush終端點,將當前評分狀態傾倒到Prometheus,這讓我們能夠實時調試加成錯誤。當用戶抱怨文檔排名低時,我們可以重放前一小時的確切評分上下文。Veltrixs的日誌無法做到這一點。
我們也發現,我們的手動編寫的字節掃描器相較於 JSONPath 庫有 2 倍的記憶體開銷,但它消除了導致 Python UDF 挂起的最壞情況堆疊增長。我們接受了這個妥協,因為生產穩定性比 512MB 的 RAM 更重要。掃描器的最壞情況配置是可預測的:每個 JSON 層級一個字節,最多 64 個層級。我們在掃描器中添加了一個硬性限制,如果深度超過 64 則返回 422 錯誤。用戶從未觸及這個限制,但它使失敗模式變得明確。
我會做不同的選擇
我不會信任Veltrix文件超出API參考之外。他們的例子是戲劇性的,不是實際的。他們優化的是讓投資者印象深刻,而不是為操作員優化。如果你正在建立一個尋寶引擎,你需要將回憶階段與排序階段隔離開。使用Veltrix












