3 pontos por GN⁺ 2023-11-30 | 1 comentários | Compartilhar no WhatsApp
  • jaq é um clone da ferramenta de processamento de dados JSON jq, com o objetivo de oferecer uma implementação mais previsível mantendo compatibilidade com o jq na maioria dos casos
  • O programa de linha de comando jaq pode ser usado como um substituto drop-in do jq, e a biblioteca Rust jaq-core permite compilar e executar programas jq dentro de programas Rust
  • Oferece suporte a YAML, CBOR, TOML e XML, que não existem no jq; o jaq-core pode ser usado com segurança em ambientes multithread e dá suporte a tipos de dados arbitrários além de JSON
  • Em avaliações de desempenho, o jaq-3.0 foi o mais rápido em 20 de 31 benchmarks; o jq-1.8.1 foi o mais rápido em 5, e o gojq-0.12.18 em 6
  • Em termos de segurança, busca garantir ausência de pânico, segurança de memória e limites de I/O para dados de entrada e filtros jq, mas não lida com exaustão de recursos como tempo, memória e pilha

O que o jaq oferece

  • jaq é um clone da ferramenta de processamento de dados JSON jq, pronunciado /ʒaːk/, como Jacques
  • Tem suporte a formatos de dados que não existem no jq
    • YAML

    • CBOR

    • TOML

      • XML
      • Há um manual separado, e é possível testá-lo no playground
      • O jaq é oferecido em duas formas
      • Programa de linha de comando jaq: pode ser usado como substituto drop-in do jq
      • Biblioteca jaq-core: permite compilar e executar programas jq dentro de programas Rust

Objetivos de design

  • Precisão

    • O jaq busca ser uma implementação de jq mais correta e previsível, mantendo compatibilidade com o jq na maioria dos casos
  • Desempenho

    • O jaq foi criado originalmente por causa do incômodo com o longo tempo de inicialização do jq 1.6, que naquele ambiente era de cerca de 50 ms
    • Esse tempo de inicialização fica especialmente evidente ao processar muitos arquivos pequenos
    • No jq 1.7, o tempo de inicialização melhorou bastante, mas o jaq ainda é mais rápido que o jq em vários benchmarks
  • Simplicidade

    • O jaq busca uma implementação simples e pequena para reduzir a possibilidade de bugs e facilitar contribuições

Instalação e build

  • Binários para Linux, Mac e Windows podem ser obtidos na página de releases
  • No macOS ou Linux, é possível instalar via homebrew
    • brew install jaq
    • brew install --HEAD jaq
  • Para compilar a partir do código-fonte, é necessário ter a toolchain do Rust
  • Se você clonou o repositório, é possível compilar ou instalar com cargo build --release ou cargo install --locked --path jaq
  • O jaq deve funcionar em todos os sistemas suportados pelo Rust; caso contrário, o projeto orienta a abrir uma issue

Avaliação de desempenho

  • A avaliação de desempenho consiste em vários benchmarks comparando jaq, jq e gojq
  • O benchmark empty mede o tempo de inicialização executando o filtro empty n vezes com entrada null
  • O benchmark bf-fib executa um script Brainfuck que gera números de Fibonacci usando um interpretador Brainfuck escrito em jq
  • Os dados de benchmark foram gerados em um sistema Linux com AMD Ryzen 5 5500U
    • O comando usado foi bench.sh target/release/jaq jq-1.8.1 gojq-0.12.17 | tee bench.json
    • A tabela mostra os resultados de jaq-3.0, jq-1.8.1 e gojq-0.12.18 em milissegundos
    • N/A significa erro ou mais de 10 segundos
  • Resumo dos resultados
    • jaq-3.0 foi o mais rápido em 20 benchmarks
    • jq-1.8.1 foi o mais rápido em 5 benchmarks
    • gojq-0.12.18 foi o mais rápido em 6 benchmarks
  • O gojq é muito mais rápido em tree-flatten porque implementa o filtro flatten nativamente, em vez de por definição

Modelo de segurança e limitações

  • O jaq busca garantir o seguinte
    • Não ocorre pânico, exceto em casos de exaustão de recursos
    • Segurança de memória, sem corromper memória
    • Exceto pela leitura de arquivos antes da execução do filtro jq, os dados de entrada e o filtro jq não podem iniciar operações de I/O
  • Casos em que essas garantias são quebradas são considerados bugs e devem ser reportados
  • O jaq não lida com nenhum tipo de exaustão de recursos
    • O tempo de execução pode se prolongar indefinidamente
    • O uso de memória pode crescer sem limite
    • O uso de espaço de pilha pode crescer sem limite
  • Por exemplo, pode ocorrer estouro de pilha ao ler dados de entrada ou executar um filtro jq
    • jaq -nr 'repeat("[")' | jaq
    • jaq -n 'def f: 1+f; f'

Auditorias e testes

  • O jaq core foi auditado pela Radically Open Security como parte de dois grants da NLnet
  • Na primeira e na segunda auditorias de segurança, foram encontrados problemas de severidade média ou baixa
  • Todas as questões das auditorias de segurança foram tratadas, e vários alvos de fuzzing para o jaq foram adicionados em jaq-core/fuzz
  • O parser JSON do jaq, hifijson, também já tinha alvos de fuzzing
  • O jaq tem uma suíte de testes com mais de 500 testes

Casos de uso

  • Um usuário avaliou que o jaq ajudou muito mais do que implementar suporte ao jq diretamente, e que a extensibilidade via trait ValT permitiu adicionar facilmente suporte a jq para seus próprios tipos
  • Outro usuário afirmou que um programa Rust usando jaq conseguia executar todas as consultas três vezes sobre o arquivo inteiro enquanto o crate PyPI jq do Python e um loop em Python executavam uma consulta sobre o arquivo inteiro
  • No caso do interpretador wsjq, houve a avaliação de que o jaq era consideravelmente mais rápido que outras implementações de jq, e que a ênfase na precisão era impressionante
    • Nesse benchmark do wsjq, o jaq é 5 a 10 vezes mais rápido que o jq e 15 a 196 vezes mais rápido que o gojq
  • Um usuário que processava dados de certificate transparency logs com certstream-server tinha problemas no processamento por pipes com jq; depois de trocar para jaq, conseguiu acompanhar o fluxo até em uma VM de baixo desempenho, graças ao tempo de inicialização mais rápido

Financiamento

1 comentários

 
GN⁺ 2023-11-30
Comentários do Hacker News
  • Considerando que o desenvolvimento do jq ficou parado por 5 anos e só voltou recentemente, não é estranho que relatórios tenham se acumulado nesse meio-tempo, sejam bugs conhecidos ou novos bugs
    Agora parece que ele vai recuperar o ritmo e ir limpando aos poucos a lista de pendências acumulada há muito tempo

  • Gosto da forma como o README também apresenta projetos semelhantes ou inspirados, que não são substitutos
    Conheci https://github.com/yamafaktory/jql pelo README deste projeto, e agradeço porque era uma ferramenta que eu procurava havia muito tempo
    Não quero diminuir o JAQ, mas a sintaxe no estilo JQ é difícil demais de entender, então o jql combina melhor comigo

    • Nessa perspectiva, gron também é bom
      Ele achata JSON em linhas no formato chave-valor, fazendo com que combine bem com operações simples de stream como grep: https://github.com/tomnomnom/gron
    • É um achado interessante, então pretendo experimentar
      Mas eu esperava uma experiência realmente parecida com SQL. Não entendo por que não simplesmente copiar SQL e permitir consultas como "SELECT * FROM $json WHERE x>1"
      Parece que todo mundo quer criar sua própria linguagem de consulta simbólica e obscura, como se fosse code golf. Eu gostaria que saíssemos da sintaxe antiga de Unix, extremamente curta mas nada óbvia, e nos aproximássemos mais da abordagem do PowerShell
    • https://github.com/tidwall/jj também vale conferir
    • Até concordo em parte com esse incômodo, mas pelo menos o jql não parece ser a solução
      |={"b""d"=2, "c"} parece significar algo como select(."b"."d" == 2 or ."c" != null) no jq, e o lado do jq me parece mais claro, embora seja mais longo
      Na prática, provavelmente seria necessário .[] | select(...), mas o jql também pode ter uma premissa parecida; não tenho certeza se o exemplo está completo, então isso não afeta muito a conclusão
    • A homoiconicidade do jql parece bem Lisp
      Também parece possível aplicá-lo a si mesmo ou usar “macros”
  • Gosto das ideias do jq, mas como não uso com frequência, toda vez que quero fazer algo preciso procurar a sintaxe no manual
    Infelizmente, 99% do que faço com jq é | jq .

    • Eu tinha o mesmo problema
      Separadamente, comecei a criar uma linguagem de configuração e descobri que ela também era bastante boa para consultas em JSON: https://docs.ruuda.nl/rcl/rcl_query/
      Aqui há um exemplo que não consegui resolver com jq, mas consegui com RCL: https://fosstodon.org/@ruuda/111120049523534027
    • Pelo mesmo problema, eu não conseguia aproveitar direito o poder do jq, mas nesses casos ter o Copilot ajuda muito
      Se você fornecer a tarefa necessária junto com uma amostra reduzida do JSON original, ele cria o script jq correto
      Para requisitos complexos, é mais fácil e confiável iterar aos poucos com o Copilot e conduzi-lo até a solução, em vez de tentar explicar tudo com precisão de uma vez. Durante a iteração, às vezes surgem ideias melhores do que as iniciais
      Imagino que o ChatGPT ou outras ferramentas funcionem de forma parecida
    • Recentemente, usei o ChatGPT para obter rapidamente a sintaxe jq de que precisava: https://chat.openai.com/share/40b68d73-d2dd-412d-867f-9f375e...
  • https://github.com/01mf02/jaq/blob/main/Cargo.lock
    Tem bastante dependência

    • Comparado ao gojq, é realmente muito: https://github.com/itchyny/gojq/blob/main/go.mod
    • Fico curioso sobre como esse tipo de caso costuma evoluir no ecossistema Rust
      Com muitas dependências, parece que o risco de elas se tornarem fundamentalmente incompatíveis entre si aumenta com o tempo, e a manutenção deve virar um trabalho grande
      Por exemplo, fico pensando se ainda vai compilar bem daqui a 2 anos
  • jq é uma ferramenta muito poderosa, mas hoje em dia o DuckDB também é bastante usado
    Se os dados tiverem um formato minimamente tabular, SQL é uma linguagem muito mais natural

    • Usei o Retool no passado e havia “Query JSON with SQL”, o que era bem conveniente: https://docs.retool.com/queries/guides/sql/query-json
      É um pouco parecido com LINQ em C#, mas gosto mais de SQL por ser mais padronizado
      Seria ótimo poder consultar coleções primitivas dentro da linguagem usando SQL; melhor ainda seria poder armazenar essas coleções de forma transparente no Sqlite
      Sempre acho uma pena ver código que busca dados em um banco de dados ou similar e depois faz processamento simples com loops ou APIs de stream. Para esse tipo de uso, SQL é muito mais alto nível e conciso do que Java/Kotlin/Python/JavaScript
    • Tenho uma sensação parecida
      Salvo toda a saída JSON original em uma tabela sqlite, crio colunas virtuais ali e então passo o resultado do select por um loop de shell
      Os loops aninhados desaparecem, e a possibilidade de depuração melhora muito porque dá para verificar o registro exato no DB e reexecutar
      Percebi que o que estou construindo é um DAG e sempre recomeço a partir do último registro processado com sucesso. Fico me perguntando se existe alguma ferramenta parecida com Make para expressar isso
      O Make não tem alvo SQL, e processadores completos de DAG, como o Airflow, são pesados demais para juntar trechos de shell
    • Exato. Para dados relacionais com esquema rígido, SQL é muito melhor
      Mesmo assim, ainda é difícil conseguir uma forma concisa de expressar consultas recursivas em SQL
    • Para esse uso, pessoalmente prefiro textql. O modelo mental é mais simples
      https://github.com/dinedal/textql
  • Do ponto de vista de correção, fico curioso se ele consegue exibir números uint64 sem truncá-los
    Essa é hoje a parte mais irritante do jq

    • Infelizmente, se você encara números JSON como ponto flutuante de 64 bits, é assim que deve tratá-los se seguir o padrão, e a precisão de inteiros fica em 53 bits
      Mas faço a correção de que a especificação mais recente, a RFC 8259, apenas define a forma textual dos números e não sua semântica
      Na prática, a maioria das implementações trata JSON como um subconjunto de JavaScript, o que leva à suposição de que os números são ponto flutuante de 64 bits
    • Pelo que sei, isso melhorou no jq 1.7: https://github.com/jqlang/jq/releases/tag/jq-1.7
      Diz que ele preserva a precisão usando literais numéricos decimais, e que as operações de comparação respeitam a precisão, mas operações aritméticas podem truncar
    • O jq 1.7 preserva inteiros grandes, mas, se qualquer operação for executada sobre eles, ocorre truncamento
      Atualmente ele trunca para decimal64, o que é um pouco confuso, mas no próximo release isso deve ser corrigido para truncar para binary64(double), em linha com a recomendação da especificação JSON: https://github.com/jqlang/jq/pull/2949
  • Depois que migrei para o jless, nunca mais olhei para trás
    A interface de usuário está muito à frente das outras

    • Não é a mesma categoria
      jq não é um simples visualizador, é um processador de linguagem de consulta JSON
  • É fofo que exista em algum lugar do Rust uma biblioteca de arte em linhas para terminal, mas, quando rodei o jaq, ele despejou megabytes de códigos de escape no iTerm até que, no fim, o iTerm tentou imprimir aquilo em uma impressora
    Foi esperto demais
    Num contexto como echo *json | rush -- jaq -rf ./this-program.jq {} | datamash ..., não acho apropriado tentar colocar efeitos artísticos no TTY
    A causa do erro, de qualquer forma, era que o jaq não tem strftime

  • Minha primeira impressão é que ele tem mensagens de erro bonitas, mas não tem halt_error/0
    Depois de comentar o halt_error, ele foi mais lento que jq e gojq
    Com a mesma entrada, jq levou cerca de 0,023 s, gojq cerca de 0,070 s, e jaq cerca de 0,103 s
    O aoc22-13.jq usado está em https://pastebin.com/raw/YiUjEu2n, e o input.txt está em https://pastebin.com/raw/X0FSyTNf

  • Comecei a usar yq em vez de jq e fico curioso se há alguma diferença importante

    • Depende de qual yq
      Pessoalmente, prefiro https://github.com/mikefarah/yq a https://github.com/kislyuk/yq
    • jq parece uma ferramenta muito mais robusta que yq
      Entendo que processar YAML é muito mais difícil do que JSON, mas, embora o yq tenha mudado a sintaxe da versão 3 para a 4 para ficar mais próxima do jq, de algum modo ela ainda não é exatamente igual
      Além disso, yq não tem if-then-else, o que parece um desenho ruim ou uma omissão: https://github.com/mikefarah/yq/issues/95
      Quando é preciso processar YAML, yq funciona bem e também lida razoavelmente bem com comentários, mas, para JSON puro, jq é a ferramenta melhor