- JSONドキュメントをパスベースで探索するRust CLIツールで、既存の
jq、jmespath、jsonpath-rust、jqlより検索速度が速い
- クエリを正規言語として表現してDFAにコンパイルし、JSONツリーを単一パスで探索する構造により**O(n)**時間で処理
- zero-copyパースをサポートする
serde_json_borrowを使用してメモリ割り当てを最小化し、ripgrepの性能哲学を参考に設計
- ベンチマーク結果では、大規模JSONでもエンドツーエンド性能が最も優秀で、検索中心のシンプルなクエリ言語を提供
- MITライセンスで公開されており、DFAベースのクエリエンジンをRustライブラリとして再利用可能
jsongrep概要
- jsongrepはJSONドキュメントでパスベースに値を検索するRust製CLIツールで、
jq、jmespath、jsonpath-rust、jqlより高速な性能を目指している
- JSONドキュメントをツリーとして捉え、パス(path) を正規言語(regular language) として表現し、DFA(Deterministic Finite Automaton) にコンパイルした後、単一パスで探索を行う
- クエリ言語はシンプルで、検索中心に設計されており、変換や計算機能はない
serde_json_borrowを利用したzero-copyパースでメモリ割り当てを最小化
ripgrepの設計哲学と性能アプローチを参考に開発された
jsongrepの使用例
- コマンド
jgはクエリとJSON入力を受け取り、パスがクエリと一致するすべての値を出力する
- ドット記法(dot path) でネストしたフィールドにアクセス
jg 'roommates[0].name' → "Alice"
- ワイルドカード(
*, [*])ですべてのキーまたはインデックスにマッチ
- Alternation(
|)で複数のパスのうち1つを選択
- 再帰探索(
(* | [*])*)で任意の深さのフィールドを検索
- Optional(
?)で0回または1回のマッチをサポート
-Fオプションで特定のフィールド名を高速に検索可能
- パイプ(
| less, | sort)を使うと自動的にパス出力を省略し、--with-pathで強制表示可能
jsongrepの中核概念
- JSONはツリー構造であり、オブジェクトキーと配列インデックスがエッジ(edge) の役割を果たす
- クエリはルートから特定ノードまでのパス集合を定義する
- クエリ言語は正規言語として設計されているためDFAに変換可能
- DFAは入力を1回だけ読み、バックトラッキングなしでO(n) 時間で探索を行う
- 既存ツール(
jq、jmespathなど)はクエリをインタプリートして再帰的に探索するが、jsongrepは事前コンパイル済みのDFAで単一パス探索を行う
DFAベースのクエリエンジン構造
- パイプラインは5段階で構成
serde_json_borrowでJSONをツリーとしてパース
- クエリをASTとしてパース
- GlushkovアルゴリズムでNFAを生成
- Subset ConstructionでDFAに変換
- DFA遷移に従ってJSONツリーを単一DFSで探索
-
クエリパース
- PEG文法(
pestライブラリ使用)でクエリをQuery ASTに変換
- 主な構文要素:
Field, Index, Range, FieldWildcard, ArrayWildcard, Optional, KleeneStar, Disjunction, Sequence
- 例:
roommates[*].name → Sequence(Field("roommates"), ArrayWildcard, Field("name"))
-
JSONツリーモデル
- オブジェクトキーと配列インデックスはエッジ、値はノード
- 例:
roommates[*].nameはroommates → [0] → nameのパスを探索
-
NFA構成 (Glushkovアルゴリズム)
- ε遷移のないNFAを生成
- 手順
- クエリシンボルに位置番号を付与
- First/Last/Follows集合を計算
- 各位置間の遷移を構成
- 例のクエリ
roommates[*].nameのNFAは4状態で構成される単純な線形構造
-
DFA変換 (Subset Construction)
- NFAの状態集合に基づいて決定性DFAを生成
- 各状態は1つのNFA状態集合に対応
Otherシンボルを追加して不要なキーを効率よくスキップ
- シンプルなクエリはNFAと同じ構造のDFAに変換される
-
DFSベースの探索
- ルートから開始して各エッジに沿ってDFA遷移を実行
- 遷移がなければそのサブツリーを枝刈り(prune)
- DFA状態がacceptingならパスと値を記録
- 各ノードは最大1回訪問され、全体の探索はO(n)
serde_json_borrowにより文字列コピーなしで元バッファを参照
ベンチマーク方法論
- Criterion.rsで統計ベースのベンチマークを実施
-
データセット
simple.json (106B), kubernetes-definitions.json (~992KB), kestra-0.19.0.json (~7.6MB), citylots.json (~190MB)
-
比較対象ツール
jsongrep, jsonpath-rust, jmespath, jaq, jql
-
ベンチマークグループ
document_parse: JSONパース速度
query_compile: クエリコンパイル時間
query_search: 探索のみ実行
end_to_end: 全体パイプライン
-
公平性への配慮
- zero-copyパースの利点は別途測定
- DFAコンパイルコストは分離して測定
- 機能のないツールは該当テストから除外
- データ複製コストは分離処理
ベンチマーク結果
- ドキュメントパース時間:
serde_json_borrowが最速
- クエリコンパイル時間:
jsongrepはDFA生成のため最も大きなコストが発生し、jmespathははるかに高速
- 検索時間:
jsongrepがすべてのツールの中で最速
- エンドツーエンド性能: 190MBデータセットでも
jq、jmespath、jsonpath-rust、jqlより圧倒的に高速
- 全結果はライブベンチマークサイトで確認可能
ライセンスと活用
- MITライセンスのオープンソースソフトウェア
- GitHub、Crates.io、Docs.rsで利用可能
- DFAベースのクエリエンジンはライブラリ形式で再利用可能で、Rustプロジェクトに直接統合できる
参考文献
- Glushkov, V. M. (1961), The Abstract Theory of Automata
- Rabin, M. O., & Scott, D. (1959), Finite Automata and Their Decision Problems
3件のコメント
すてきですね
| パイプ記号がなぜ本文では違って見えるのでしょうか? 不思議ですね..
Hacker News の意見
jqの文法があまりに難解で、毎回単純なJSONの値を1つ取り出すだけでも検索しないといけない
主に一回限りのフィルタを書くので、読む時間より書く時間のほうが長い
たぶん自分のユースケースが単純か、jqが自分の思考に合っているのだと思う
すべてのCLIツールがJSONを入出力してjqでつながる世界を夢見ているが、あなたにとっては悪夢だろう
使うたびに学び直す必要があり、直感的ではない
sedもTuring完全ではあるが、ほとんどの人は正規表現による置換くらいしか使わない
jqは好きだが、以前自分で書いたクエリを理解できないことがあった
celqはよりなじみのあるCEL言語を使う
単にJavaScriptでJSONを扱う方式だが、驚いたことにjqより速い
$ cat package.json | dq 'Object.keys(data).slice(0, 5)'のように使う私はClojureを学んだおかげで、今ではJSONの代わりにEDNを使っている
より簡潔で読みやすく、構造的にも扱いやすい
最近はborkdude/jetやbabashkaでデータを扱い、djblue/portalで可視化している
jqの複雑な演算子にこだわる理由が理解できない
性能は重要だと思うが、ナノ秒単位の比較は見せびらかしのパフォーマンスのように感じる
ほとんどの場合、今使っているツールで十分だ
例えば私は、大きなファイルのときだけgrepの代わりにrgを使う
2msと0.2msの差は些細に見えても、TB単位のストリームを処理する人には重要だ
ハードウェアは速くなったのに、ソフトウェアはむしろ遅くなっているのが現実だ
最適化を拒むのは怠慢と想像力不足のように感じる
ネットワーク遅延より速いからと安心するのは言い訳に聞こえる
もしJSONが大きすぎるなら、JSONではなくバイナリ形式を使うべきだ
CLIで複雑なパイプラインを組む必要があるなら、むしろプログラムを書くほうがよいと思う
多くの新しいCLIツールは「より速い」を売りにしているが、実際にjqが遅いと感じたことはほとんどない
jqでフィールド名を変えるだけの単純な作業でも遅すぎるので、NodeやRustのスクリプトで直接処理している
ハイパースケーラー環境では、数TBのログを直接ダウンロードして分析する
モニタリングの解像度によっては性能差を実感することがある
機能の一部しか実装せず、ベンチマークで勝利を主張するやり方だ
今回のプロジェクトも、そうした**「部分集合のほうが速い」**トレンドの一環に見える
そこからは何もかも遅く感じられる
ripgrepのように一度速いツールを使うと、元には戻りにくい
jqとyqを両方使ったことがあるが、yqはずっと遅いのに不満はなかった
jqより速いツールがあるなら素晴らしいが、それを必要とするのは特定のユーザー層だけだ
それでも最適化を愛する者として敬意を表したい
ETL段階ではかなり時間がかかる
ページを最初に開いたとき、ライトモードの色崩れがあった
ダークモードに切り替えてから戻すと直る
私は正確性のためにJaqへ乗り換えた
性能もjqよりよいらしい
jqが遅いという評判は、配布パッケージングの問題によるものに見える
仕事で**newline-delimited JSON(jsonl)**をよく扱う
各行が完全なJSONオブジェクトだが、主要なCLIツールがこの形式をサポートしているのか気になる
jq、mlr、htmlq、xsv、yqなど、さまざまなデータ処理CLIツールを使ってきたが、
Nushellを見つけてからは全部置き換えられた
1つの文法ですべてのフォーマットを扱えるのが新鮮だった
同僚と協業するときだけjq、yq、mlrも併用する
自動補完の設定やコマンドの見つけやすさに少し不便はあるが、oh-my-zshよりずっとましだ
型注釈の強制、静的バイナリのコンパイル、TUIライブラリまでそろえば、小規模アプリを書くのにも使えそうだ
素晴らしいツールだ! ただ、ベンチマークの可視化が少し惜しい
すべてのツールが同じ色なので、jsongrepがどこにあるのか見つけにくい
jq自体もグラフにないので混乱した
xLargeファイルは190MiBで小さめだが、私は400MiB〜1GiBのJSONをよく扱う
もっと大きな公開JSON文書があるなら教えてほしい
ベンチマークの可視化が粗く感じられる
色や形を活用して、より多くの次元を表現できるとよい
ファイルパスを直接読まないと結果を理解できないのは不便だ