alltools.one
JSON
2025-06-02
8 min
alltools.one Team
JSONStreamingPerformanceLarge FilesMemory

JSON Streaming para Arquivos Grandes: Processe Sem Carregar Tudo

O parsing padrão de JSON carrega o documento inteiro na memória, constrói uma estrutura de dados completa e depois dá acesso a você. Para um arquivo de 10 MB, funciona bem. Para um arquivo de 10 GB, seu processo fica sem memória e trava. Parsers de streaming resolvem isso processando o JSON incrementalmente — lendo e tratando dados conforme chegam, sem nunca manter o documento inteiro na memória.

O Problema do Parsing Padrão

import json

# Isso carrega o arquivo INTEIRO na memória
with open('huge.json') as f:
    data = json.load(f)  # Arquivo de 10 GB = 10+ GB de RAM

# O processamento acontece após o carregamento completo
for item in data['records']:
    process(item)

Para um arquivo com 1 milhão de registros de 10 KB cada, o parsing padrão precisa de:

  • Tamanho do arquivo: ~10 GB
  • Memória para parsing: ~10 GB (a string bruta)
  • Memória para estrutura de dados: ~15-20 GB (objetos Python são maiores que JSON bruto)
  • Total: ~25-30 GB de RAM

Streaming reduz isso para megabytes.

Abordagens de Streaming

Estilo SAX (Baseado em Eventos)

O parser emite eventos conforme encontra tokens JSON:

import ijson

# Processa itens um de cada vez - uso de memória constante
with open('huge.json', 'rb') as f:
    for record in ijson.items(f, 'records.item'):
        process(record)  # Cada registro é parseado individualmente
        # Registros anteriores são coletados pelo garbage collector

Os eventos incluem: start_map, map_key, end_map, start_array, end_array, string, number, boolean, null.

JSON Lines (JSONL / NDJSON)

Uma abordagem mais simples: um objeto JSON por linha. Cada linha é um documento JSON completo e válido:

{"id": 1, "name": "Alice", "email": "alice@example.com"}
{"id": 2, "name": "Bob", "email": "bob@example.com"}
{"id": 3, "name": "Charlie", "email": "charlie@example.com"}

O processamento é trivial — leia linha por linha:

with open('data.jsonl') as f:
    for line in f:
        record = json.loads(line)
        process(record)

Vantagens do JSON Lines:

  • Cada linha é parseável independentemente (processamento paralelo)
  • Amigável para adição (basta adicionar uma nova linha)
  • Funciona com ferramentas padrão do Unix (grep, wc, head, tail)
  • Natural para arquivos de log e dados de streaming

Processamento em Blocos

Para arrays JSON padrão, divida o processamento em blocos:

import ijson

def process_in_chunks(filename, chunk_size=1000):
    chunk = []
    with open(filename, 'rb') as f:
        for record in ijson.items(f, 'item'):
            chunk.append(record)
            if len(chunk) >= chunk_size:
                process_batch(chunk)
                chunk = []
    if chunk:
        process_batch(chunk)

Implementações por Linguagem

Python (ijson)

import ijson

# Stream a partir de arquivo
with open('large.json', 'rb') as f:
    parser = ijson.parse(f)
    for prefix, event, value in parser:
        if prefix == 'records.item.name':
            print(value)

# Stream a partir de resposta HTTP
import urllib.request
response = urllib.request.urlopen('https://api.example.com/data')
for record in ijson.items(response, 'records.item'):
    process(record)

JavaScript (Node.js)

const { createReadStream } = require('fs');
const { parser } = require('stream-json');
const { streamArray } = require('stream-json/streamers/StreamArray');

const pipeline = createReadStream('large.json')
  .pipe(parser())
  .pipe(streamArray());

pipeline.on('data', ({ value }) => {
  process(value);
});

pipeline.on('end', () => {
  console.log('Done processing');
});

Linha de Comando (jq)

# Modo stream - processa objetos individualmente
jq --stream 'select(length == 2) | .[1]' large.json

# Processa JSON Lines
cat data.jsonl | jq -c 'select(.age > 30)'

# Converte array para JSON Lines
jq -c '.[]' large_array.json > data.jsonl

Quando Usar Streaming

CenárioPadrãoStreaming
Arquivo abaixo de 100 MBPreferívelExagero
Arquivo de 100 MB a 1 GBDepende da RAMRecomendado
Arquivo acima de 1 GBInviávelObrigatório
Resposta HTTP (grande)Risco de timeoutStream conforme recebido
Feed de dados em tempo realNão aplicávelObrigatório
Leitura simples e únicaPreferívelDesnecessário

Comparação de Desempenho

Processando 1 milhão de registros (arquivo de 1 GB):

AbordagemUso de MemóriaTempo de ProcessamentoComplexidade
json.load()3-5 GB15 segSimples
ijson streaming50 MB45 segModerada
JSON Lines10 MB12 segSimples
Em blocos (1000)100 MB20 segModerada

Streaming usa muito menos memória, mas é mais lento para parsing estilo SAX devido à sobrecarga baseada em eventos. JSON Lines é tanto rápido quanto eficiente em memória porque cada linha é uma operação de parse independente.

Boas Práticas

  1. Use JSON Lines quando possível: É o formato de streaming mais simples e funciona com ferramentas padrão.
  2. Configure tamanhos de buffer: Configure buffers de leitura para throughput ideal (64 KB a 1 MB tipicamente).
  3. Processe em lotes: Agrupe inserções no banco de dados e chamadas de API em vez de processar um registro por vez.
  4. Trate erros graciosamente: No streaming, um registro malformado não deve derrubar o pipeline inteiro.
  5. Monitore a memória: Use profiling para verificar se o streaming está realmente mantendo a memória limitada.

Para formatar e validar arquivos JSON menores, nosso Formatador JSON lida com documentos de até 100 MB com formatação em tempo real.

FAQ

Posso usar JSONPath com parsers de streaming?

Algumas bibliotecas de streaming suportam filtragem baseada em caminho. O ijson.items do Python suporta filtragem por caminho durante o streaming. No entanto, consultas JSONPath complexas (filtros, curingas entre níveis) tipicamente exigem o documento completo em memória. Para consultas baseadas em caminho, veja nosso guia de JSONPath.

Como converto um array JSON grande para JSON Lines?

Use jq -c '.[]' input.json > output.jsonl para arquivos que cabem na memória. Para arquivos realmente grandes, use um conversor de streaming: leia o array com um parser de streaming e escreva cada elemento como uma linha.

Recursos Relacionados

Published on 2025-06-02
JSON Streaming for Large Files: Process Without Loading All | alltools.one