3P by GN⁺ | ★ favorite | 댓글 1개
  • 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는 두 가지 형태로 제공됨
      • 명령줄 프로그램 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 --releasecargo install --locked --path jaq로 빌드 또는 설치 가능함
  • jaq는 Rust가 지원하는 모든 시스템에서 동작해야 하며, 그렇지 않으면 이슈를 등록해달라고 안내함

성능 평가

  • 성능 평가는 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.0, jq-1.8.1, gojq-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는 두 차례의 NLnet grant의 일부로 Radically Open Security의 감사를 받음
  • 첫 번째두 번째 보안 감사에서는 중간 또는 낮은 심각도의 이슈들이 발견됨
  • 보안 감사의 모든 이슈는 처리됐고, jaq-core/fuzz에 jaq용 퍼징 대상이 여러 개 추가됨
  • jaq의 JSON 파서 hifijson도 이미 퍼징 대상을 갖고 있었음
  • jaq에는 500개 이상 테스트로 구성된 테스트 스위트가 있음

사용자 사례

  • 한 사용자는 jaq가 직접 jq 지원을 구현하는 것보다 큰 도움을 줬고, ValT trait을 통한 확장성 덕분에 자체 타입에 jq 지원을 쉽게 추가할 수 있었다고 평가함
  • 다른 사용자는 jaq를 쓰는 Rust 프로그램이 Python의 jq PyPI crate와 Python 루프가 전체 파일에 대해 쿼리 하나를 실행하는 동안, 전체 파일에 대해 모든 쿼리를 세 번 실행할 수 있었다고 밝힘
  • wsjq 인터프리터 사례에서는 jaq가 다른 jq 구현보다 상당히 빠르고 정확성에 대한 강조가 인상적이었다는 평가가 있음
    • 해당 wsjq 벤치마크에서 jaq는 jq보다 5~10배, gojq보다 15~196배 빠름
  • certificate transparency log 데이터를 certstream-server로 처리하던 사용자는 jq 파이프 처리에서 문제가 있었고, jaq로 바꾼 뒤 더 빠른 시작 시간 덕분에 저사양 VM에서도 따라갈 수 있었다고 함

자금 지원

댓글과 토론

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
      십진수 숫자 리터럴을 사용해 정밀도를 보존하고, 비교 연산은 정밀도를 존중하지만 산술 연산은 잘릴 수 있다고 되어 있음
    • 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에 예술적 효과를 넣으려는 게 적절하지 않다고 봄
    에러의 원인은 어쨌든 jaqstrftime 이 없다는 점이었음

  • 첫인상은 멋진 에러 메시지는 있지만 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가 더 나은 도구임