Jetro: 高性能Rust JSONクエリエンジン

JSONは現代のウェブにおける主要なデータ交換形式であり続けていますが、大規模な環境で効率的にクエリを実行することは、しばしばボトルネックとなります。jqはJSON処理における広く受け入れられている業界標準ですが、高性能なRustアプリケーションに組み込むことは必ずしも容易ではありません。そこで、表現力豊かで親しみやすい設計を目指した、Rustで書かれた新しいJSONクエリエンジン、Jetroが登場しました。

Jetroは単なるjqのクローンではなく、JSONクエリの実行方法を再考したものです。関数型プログラミングのパラダイムとプランナー駆動のアーキテクチャを活用することで、Jetroはドメイン固有言語(DSL)のパワーを提供しつつ、パフォーマンスとリソース効率を優先した、より合理化された組み込み可能なエンジンを目指しています。

アーキテクチャ: プランナー駆動の実行

Jetroの最も重要な技術的な違いの一つは、クエリ実行へのアプローチです。多くの伝統的なクエリ言語では、マッピング、フィルタリング、収集といった一連の操作が、実体化されたステージのシーケンスとして実行されます。例えば、あるクエリが注文リストをフィルタリングし、その後に最初の結果だけを取得する場合、単純な実装では、次のステージに結果を渡す前にリスト内のすべての注文を処理してしまいます。

Jetroは、demand propagation(要求伝播)と呼ばれる手法を採用しています。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が1つ必要か? はい。一致する注文が1つ必要か? はい。注文を1つ取り出し、その合計を計算し、もしフィルタに一致すれば、IDを出力して直ちに停止する」。

このアプローチは、特に、必要なデータが残りのデータのわずかなサブセットであるような大規模なJSONドキュメントを扱う場合に、CPUとメモリのオーバーヘッドを大幅に削減します。

表現力と関数型パラダイム

JetroのDSLは関数型言語に触発されており、開発者が複雑なデータ変換を行えるようにします。オブジェクトの形状を整える高度なクエリ、オブジェクトマッピング、パターンマッチングをサポートしています。

例えば、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は主要なJSONパーサーとしてsimd-jsonを使用し、SIMD(Single Instruction, Multiple Data)命令を活用してパースプロセスを加速させます。高速なパーサーとdemand-propagation実行モデルを組み合わせることで、Jetroは不要な作業を避け、中間結果の冗長な実体化を回避します。

ライブラリだけでなく、プロジェクトは、jq-のような体験を好むターミナルユーザー向けにjetrocliを提供しており、さらに言語を学習するための包括的なブックを提供しています。

標準化の課題

Jetroはjqに代わる強力な選択肢を提供しますが、コミュニティの議論におけるより広いエコシステムは、一つの課題をハイライトしています。それは、jq言語の正式な仕様が存在しないことです。Hacker Newsのあるユーザーが指摘したように、jqの実装は複数存在しますが(PythonのyqやGoのyqを含む)、それらはしばしば「ほとんどは互換性がある」程度に留まります。

"実装間のわずかな違いが、足元をすくう原因になる..."

Jetroのアプローチ(jqの関数型パワーを維持しつつ、より小さく、より親しみやすいクエリ言語を作成すること)は、単一の、一貫性のあるエンジンをRustアプリケーションに直接組み込めるように提供することで、この曖昧昧さを解消します。

Sources