3 ポイント 投稿者 GN⁺ 2023-11-30 | 1件のコメント | WhatsAppで共有
  • jaqはJSONデータ処理ツールjqのクローンで、多くの場合でjqとの互換性を保ちながら、より予測可能な実装を目指すもの
  • コマンドラインプログラムjaqjqドロップイン代替として利用でき、Rustライブラリjaq-coreはRustプログラム内でjqプログラムをコンパイル・実行できる
  • jqにはないYAML、CBOR、TOML、XMLのサポートを提供し、jaq-coreはマルチスレッド環境で安全に利用でき、JSONを超えた任意のデータ型をサポートする
  • 性能評価では、jaq-3.0は31件のベンチマークのうち20件で最速、jq-1.8.1は5件、gojq-0.12.18は6件で最速だった
  • セキュリティ面では、パニック防止、メモリ安全性、入力データとjqフィルタのI/O制限を保証しようとしているが、時間・メモリ・スタックなどのリソース枯渇には対応しない

jaqが提供するもの

  • jaqはJSONデータ処理ツールjqのクローンで、発音は/ʒaːk/Jacquesと同じ
  • jqにはないデータ形式のサポートがある
    • YAML

    • CBOR

    • TOML

      • XML
      • 別途manualがあり、playgroundで試せる
      • jaqは2つの形態で提供される
      • コマンドラインプログラムjaq: jqドロップイン代替として利用可能
      • ライブラリjaq-core: Rustプログラム内でjqプログラムをコンパイルして実行可能

設計目標

  • 正確性

    • jaqは多くの場合でjqとの互換性を保ちながら、より正確で予測可能なjq実装を目指す
  • 性能

    • jaqはもともとjq 1.6の長い起動時間が不便だったため作られたもので、その環境での起動時間は約50msだった
    • 多数の小さなファイルを処理する場合、この起動時間が特に目立つ
    • jq 1.7で起動時間は大きく改善されたが、jaqは複数のベンチマークで今もjqより速い
  • シンプルさ

    • jaqはバグの可能性を減らし、コントリビュートしやすくするため、シンプルで小さな実装を志向する

インストールとビルド

  • Linux、Mac、Windows向けのバイナリはreleases pageから入手できる
  • macOSまたはLinuxではhomebrewでインストール可能
    • brew install jaq
    • brew install --HEAD jaq
  • ソースからビルドするにはRustツールチェーンが必要
  • リポジトリをクローンした場合は、cargo build --releaseまたはcargo install --locked --path jaqでビルドまたはインストール可能
  • jaqはRustがサポートするすべてのシステムで動作するはずで、そうでない場合はIssueを登録してほしいと案内している

性能評価

  • 性能評価は、jaq、jq、gojqを比較する複数のベンチマークで構成される
  • emptyベンチマークは、null入力でemptyフィルタをn回実行し、起動時間を測定する
  • bf-fibベンチマークは、jqで書かれたBrainfuckインタプリタで、Fibonacci数を生成するBrainfuckスクリプトを実行する
  • ベンチマークデータはLinuxシステム上のAMD Ryzen 5 5500Uで生成された
    • 使用コマンドはbench.sh target/release/jaq jq-1.8.1 gojq-0.12.17 | tee bench.json
    • 表にはjaq-3.0jq-1.8.1gojq-0.12.18の結果がミリ秒単位で表示される
    • N/Aはエラーまたは10秒超過を意味する
  • 結果の要約
    • jaq-3.020件のベンチマークで最速
    • jq-1.8.1は5件のベンチマークで最速
    • gojq-0.12.18は6件のベンチマークで最速
  • gojqtree-flattenで大幅に速いが、これはflattenフィルタを定義ではなくネイティブに実装しているため

セキュリティモデルと制限

  • jaqは以下を保証しようとしている
    • リソース枯渇のケースを除き、パニックが発生しない
    • メモリを破壊しないメモリ安全性
    • jqフィルタ実行前のファイル読み込みを除けば、入力データとjqフィルタがI/O操作を開始できない
  • この保証が破られる場合はバグとみなされ、報告対象となる
  • jaqはいかなる種類のリソース枯渇にも対応しない
    • 実行時間が無制限に長くなり得る
    • メモリを無制限に使用し得る
    • スタック領域を無制限に使用し得る
  • 例として、入力データを読み込むときやjqフィルタを実行するときにスタックオーバーフローが発生し得る
    • jaq -nr 'repeat("[")' | jaq
    • jaq -n 'def f: 1+f; f'

監査とテスト

  • jaq coreは2回のNLnet grantの一部として、Radically Open Securityの監査を受けた
  • 1回目2回目のセキュリティ監査では、中程度または低い深刻度の問題が見つかった
  • セキュリティ監査のすべての問題は対応済みで、jaq-core/fuzzにjaq向けのファジング対象が複数追加された
  • jaqのJSONパーサーhifijsonにも、すでにファジング対象があった
  • jaqには500件以上のテストで構成されるテストスイートがある

ユーザー事例

  • あるユーザーは、jaqが直接jqサポートを実装するより大いに役立ち、ValT traitによる拡張性のおかげで独自型にjqサポートを簡単に追加できたと評価している
  • 別のユーザーは、jaqを使うRustプログラムが、Pythonのjq PyPI crateとPythonループがファイル全体に対して1つのクエリを実行している間に、ファイル全体に対してすべてのクエリを3回実行できたと述べている
  • wsjqインタプリタの事例では、jaqが他のjq実装よりかなり速く、正確性を重視している点が印象的だったとの評価がある
    • そのwsjqベンチマークで、jaqはjqより5〜10倍、gojqより15〜196倍速い
  • certificate transparency logデータをcertstream-serverで処理していたユーザーは、jqパイプ処理で問題があり、jaqに切り替えた後はより速い起動時間のおかげで低スペックVMでも追従できたという

資金提供

1件のコメント

 
GN⁺ 2023-11-30
Hacker News のコメント
  • jq の開発が5年間止まっていて、最近ようやく再び動き出したことを考えると、既知のバグであれ新しいバグであれ、その間に報告が積み上がっていたのは不思議ではない
    これから勢いを取り戻して、長く積み上がっていた未解決リストを少しずつ片付けていくのだと思う

  • 似ている、または影響を受けたプロジェクトで、代替ではないものを README で一緒に紹介してくれるやり方は良いと思う
    このプロジェクトの README で https://github.com/yamafaktory/jql を知った。ずっと探していたツールなのでありがたい
    JAQ をけなすつもりはないが、JQ 風の構文はあまりに理解しづらいので、自分には jql のほうが合っている

    • この観点では gron も良い
      JSON をキー・値形式の行に平坦化して、grep のような単純なストリーム処理とうまく合うようにしてくれる: https://github.com/tomnomnom/gron
    • 良い発見なので一度使ってみるつもり
      ただし、本当に SQL のような体験を期待していた。なぜ単に SQL をまねて、"SELECT * FROM $json WHERE x>1" のようにクエリできるようにしないのか分からない
      皆、コードゴルフでもするかのように、自分だけの難解な記号式クエリ言語を作りたがっているように見える。極端に短いが自明ではない昔の Unix 風構文から離れて、PowerShell 寄りのやり方に近づいてほしい
    • https://github.com/tidwall/jj も確認する価値がある
    • そうした不便さにはある程度共感するが、少なくとも jql が解決策に見えるわけではない
      |={"b""d"=2, "c"} は jq の select(."b"."d" == 2 or ."c" != null) のような意味に見えるが、jq のほうが長くてもより明確に感じる
      実際には .[] | select(...) が必要だろうが、jql にも似た前提があるかもしれず、例が完全なのか確信がないので、結論には大きく影響しない
    • jql の**同図像性(homoiconicity)**はかなり Lisp っぽく見える
      自分自身に適用したり、「マクロ」を使ったりすることもできそうだ
  • jq のアイデアは好きだが、頻繁に使うわけではないので、やりたいことをするには毎回構文をマニュアルで調べる必要がある
    残念ながら、jq でやることの 99% は | jq .

    • 同じ問題があった
      別途、設定言語を作り始めたのだが、実は JSON クエリにもかなり良いことが分かった: https://docs.ruuda.nl/rcl/rcl_query/
      jq では解けなかったが RCL では解けた例がここにある: https://fosstodon.org/@ruuda/111120049523534027
    • 同じ問題のせいで jq の強力さを十分に活用できていなかったが、こういう場合には Copilot があって本当に助かる
      必要な作業と、縮小した元 JSON サンプルを一緒に渡すと、正しい jq スクリプトを作ってくれる
      複雑な要件は一度で正確に説明するより、Copilot と少しずつ反復しながら解法へ誘導するほうが簡単で安定している。反復しているうちに、最初より良いアイデアが浮かぶこともある
      ChatGPT や他のツールも似たように動作しそうだ
    • 最近は ChatGPT で必要な jq 構文を素早く得られた: https://chat.openai.com/share/40b68d73-d2dd-412d-867f-9f375e...
  • https://github.com/01mf02/jaq/blob/main/Cargo.lock
    依存関係がかなり多い

    • gojq と比べると本当に多い: https://github.com/itchyny/gojq/blob/main/go.mod
    • Rust エコシステムでは、こういう場合は普通どう推移するのか気になる
      依存関係が多いと、時間が経つにつれて互いに根本的に互換性がなくなるリスクが高そうで、保守が大仕事になりそうだ
      例えば、2年後にもコンパイルは問題なくできるのだろうか
  • jqは非常に強力なツールだが、最近はDuckDBもよく使われている
    データがある程度表形式なら、SQLのほうがはるかに自然な言語だ

    • 以前Retoolを使ってみたとき、「Query JSON with SQL」があり、かなり便利だった: https://docs.retool.com/queries/guides/sql/query-json
      C#のLINQに少し似ているが、SQLのほうがより標準化されているので好みだ
      言語内で生のコレクションをSQLでクエリできると素晴らしいし、さらにコレクションを透過的にSqliteへ保存できるならもっとよい
      データベースなどからデータを取得したあと、ループやストリームAPIで単純に処理するコードを見ると、いつも惜しいと感じる。この用途では、SQLはJava/Kotlin/Python/JavaScriptよりはるかに高水準で簡潔だ
    • 同じように感じる
      元のJSON出力をすべてsqliteテーブルに保存し、そこで仮想カラムを作ってから、select結果をシェルのループで回している
      ネストしたループが解消され、正確なレコードをDBで確認して再実行できるので、デバッグ可能性がずっと良くなる
      作っているものがDAGだと気づき、常に最後に成功処理されたレコードから再開している。これを表現できるMakeのようなツールがあるのか気になっている
      MakeにはSQLターゲットがなく、Airflowのような本格的なDAG処理系はシェル片をつなげるには重すぎる
    • その通り。厳密なスキーマのあるリレーショナルデータにはSQLのほうがずっと良い
      それでも、SQLで再帰クエリを簡潔に表現する方法は依然としてなかなか得られない
    • この用途には個人的にはtextqlのほうが良い。頭の中のモデルがより単純だ
      https://github.com/dinedal/textql
  • 正確性の観点では、uint64数値を欠けることなく表示できるのか気になる
    いまのjqでいちばん気になる点だ

    • 残念ながらJSONの数値を64ビット浮動小数点として見るなら、標準に従う限りそう扱うべきで、整数精度は53ビットになる
      ただし最新仕様であるRFC 8259は、数値のテキスト形式だけを規定し、意味論は定めないと修正している
      実際にはほとんどの実装がJSONをJavaScriptのサブセットのように扱うため、数値は64ビット浮動小数点だという前提につながっている
    • jq 1.7で改善されたと理解している: https://github.com/jqlang/jq/releases/tag/jq-1.7
      10進数の数値リテラルを使って精度を保持し、比較演算は精度を尊重するが、算術演算では切り詰められる可能性がある、とされている
    • jq 1.7は大きな整数を保持するが、その上で何らかの演算が行われると切り詰められる
      現在はdecimal64に切り詰めるので少し紛らわしいが、次のリリースではJSON仕様の提案に合わせてbinary64(double) に切り詰めるよう修正される予定だ: https://github.com/jqlang/jq/pull/2949
  • jlessに乗り換えてから振り返っていない
    ユーザーインターフェースが他のものよりはるかに先を行っている

    • 同じ種類のものではない
      jqは単なるビューアではなく、JSONクエリ言語プロセッサ
  • Rustのどこかにターミナルのラインアート用ライブラリがあるのはかわいいが、jaqを実行してみたところ、iTermにエスケープコードをメガバイト単位で吐き出し、最終的にiTermがプリンタへ出力しようとした
    賢く振る舞いすぎていた
    echo *json | rush -- jaq -rf ./this-program.jq {} | datamash ...のような文脈では、TTYに芸術的な効果を入れようとするのは適切ではないと思う
    エラーの原因は、いずれにせよjaqに**strftime** がないことだった

  • 第一印象は、格好いいエラーメッセージはあるが**halt_error/0** がない、というものだ
    halt_errorをコメントアウトしたあとは、jqやgojqより遅かった
    同じ入力で、jqは約0.023秒、gojqは約0.070秒、jaqは約0.103秒かかった
    使用したaoc22-13.jqhttps://pastebin.com/raw/YiUjEu2nで、input.txthttps://pastebin.com/raw/X0FSyTNf

  • jqの代わりにyqを使い始めたのだが、重要な違いがあるのか気になる

    • どのyqかによる
      個人的にはhttps://github.com/mikefarah/yqhttps://github.com/kislyuk/yqより好む
    • jqはyqよりもずっと堅牢なツールに感じる
      YAMLの処理がJSONよりはるかに難しいことは理解しているが、yqはバージョン3から4にかけて構文をjqに近づけたにもかかわらず、どういうわけか完全には同じではない
      またyqにはif-then-elseがないため、設計がよくないか、抜け落ちているように見える: https://github.com/mikefarah/yq/issues/95
      YAMLを処理する必要があるときはyqはうまく動き、コメントもかなりうまく扱うが、純粋なJSON処理にはjqのほうが優れたツールだ