Jetro: 一款高效能的 Rust JSON 查詢引擎
JSON 仍是現代網路的主要數據交換格式,但在大規模應用中高效地查詢 JSON 往往會成為瓶頸。雖然 jq 是 JSON 處理領域廣受認可的業界標準,但要將其嵌入到高效能的 Rust 應用程式中並不總是那麼簡單。這就是 Jetro 的用武之地,這是一款使用 Rust 編寫的新型 JSON 查詢引擎,旨在兼具表達力與易用性。
Jetro 不僅僅是 jq 的克隆版,而是對 JSON 查詢執行方式的重新思考。透過利用函數式編程範式與規劃器驅動(planner-driven)架構,Jetro 旨在提供領域特定語言(DSL)的強大功能,同時提供一個更流暢、可嵌入的引擎,並優先考慮效能與資源效率。
架構:規劃器驅動的執行方式
Jetro 最顯著的技術差異之一在於其查詢執行方式。在許多傳統的查詢語言中,一系列的操作——例如映射(mapping)、過濾(filtering)與收集(collecting)——是以一系列實體化階段(materialized stages)的形式來執行的。例如,如果一個查詢先過濾訂單列表,然後僅取第一個結果,傳統的實作方式會在將結果傳遞給下一個階段之前,處理列表中的每一個訂單。
Jetro 採用了一種稱為**需求傳播(demand propagation)**的技術。需求傳播允許引擎從查詢鏈的末端向後讀取,藉此精確地確定滿足查詢所需的內容。引擎並非處理整個數據集,而是透過一種延遲(lazy)、基於拉取(pull-based)的模型,讓元素在流水線中流動。
請考慮以下查詢:
let out = j.collect("$.orders .map({ id: @.id, total: @.items.map(@.price * @.qty).sum() }) .filter(@.total > 100) .map(@.id) .first()");
在 Jetro 中,這並不會實體化所有的訂單並為每一筆計算總額。相反地,引擎會詢問:「我需要一個 ID 嗎?是的。我需要一個符合條件的訂單嗎?是的。拉取一個訂單,計算其總額,如果它符合過濾條件,就輸出該 ID 並立即停止。」
這種方法顯著降低了 CPU 與記憶體開銷,特別是在處理大型 JSON 文件時,其中僅需的數據僅是其一小部分。
表達力與函數式範式
Jetro 的 DSL 靈感來自函數式語言,允許開發者構建複雜的數據轉換邏輯。它支援進階的物件形狀調整查詢、物件映射以及模式匹配(pattern matching)。
例如,Jetro 可以在查詢中處理複雜的條件邏輯,如下例所示:
{
errors: $.events .drop_while(@.level != 'error') .filter(@.service == 'checkout') .map(match @ with {
{ level: 'error', message: msg, timestamp: ts } -> { kind: 'error', ts: ts, msg: msg },
{ level: 'error', message: msg } -> { kind: 'warning', msg: msg },
_ -> { kind: 'other' }
}) .take(20),
slow_orders: $.orders .filter(@.latency_ms > 500) .map({ id: @.id, latency: @.latency_ms }) .take(10),
first_vip: $.customers .filter(@.tier == 'vip') .map({ id: @.id, region: @.region }) .first()
}
這種程度的表達力讓轉換邏輯可以成為查詢的一部分,從而讓應用程式的其他部分能接收到乾淨且最終的結果。
效能與整合
為了確保最大吞吐量,Jetro 使用 simd-json 作為其主要的 JSON 解析器,利用 SIMD(單指令多數據)指令來加速解析過程。透過將快速的解析器與需求傳播的執行模型結合,Jetro 避免了不必要的作業,並避免了中間結果的冗餘實體化。
除了函式庫之外,該專案還提供了一個 jetrocli 供終端機用戶使用(提供類似 jq 的體驗),以及一本詳盡的指南供學習該語言。
標準化的挑戰
雖然 Jetro 為 jq 提供了一個強大的替代方案,但社群討論的廣泛生態系也凸顯了一個挑戰:jq 語言缺乏正式的規範。正如 Hacker News 上的一位用戶所指出的,目前有多種 jq 的實作方式(包括 Python 的 yq 與 Go 的 yq),但它們通常僅是「大致相容」。
"實作之間的微小差異正是它們會讓你栽跟頭的地方..."
Jetro 的方法——在保持 jq 的函數式強大功能的同時,創造一種更小巧、更易於上手的新型查詢語言——解決了這種模糊性,透過提供一個單一且一致的引擎,可以直接嵌入到 Rust 應用程式中。