JSON Streaming para Archivos Grandes: Procesar Sin Cargar Todo
El parseo estándar de JSON carga el documento completo en memoria, construye una estructura de datos completa y luego te da acceso. Para un archivo de 10 MB, eso funciona bien. Para un archivo de 10 GB, tu proceso se queda sin memoria y se bloquea. Los parsers de streaming resuelven esto procesando JSON incrementalmente — leyendo y manejando datos conforme llegan, sin mantener nunca el documento completo en memoria.
El Problema con el Parseo Estándar
import json
# Esto carga el archivo COMPLETO en memoria
with open('huge.json') as f:
data = json.load(f) # Archivo de 10 GB = 10+ GB de RAM
# El procesamiento ocurre después de la carga completa
for item in data['records']:
process(item)
Para un archivo con 1 millón de registros de 10 KB cada uno, el parseo estándar necesita:
- Tamaño del archivo: ~10 GB
- Memoria para parseo: ~10 GB (la cadena cruda)
- Memoria para estructura de datos: ~15-20 GB (los objetos Python son más grandes que el JSON crudo)
- Total: ~25-30 GB de RAM
El streaming reduce esto a megabytes.
Enfoques de Streaming
Estilo SAX (Basado en Eventos)
El parser emite eventos conforme encuentra tokens JSON:
import ijson
# Procesa elementos uno a la vez - uso de memoria constante
with open('huge.json', 'rb') as f:
for record in ijson.items(f, 'records.item'):
process(record) # Cada registro se parsea individualmente
# Los registros anteriores son recolectados por el garbage collector
Los eventos incluyen: start_map, map_key, end_map, start_array, end_array, string, number, boolean, null.
JSON Lines (JSONL / NDJSON)
Un enfoque más simple: un objeto JSON por línea. Cada línea es un documento JSON completo y 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"}
El procesamiento es trivial — lee línea por línea:
with open('data.jsonl') as f:
for line in f:
record = json.loads(line)
process(record)
Ventajas de JSON Lines:
- Cada línea es parseable independientemente (procesamiento paralelo)
- Amigable para agregar datos (solo añade una nueva línea)
- Funciona con herramientas estándar de Unix (
grep,wc,head,tail) - Natural para archivos de log y datos en streaming
Procesamiento por Chunks
Para arrays JSON estándar, divide el procesamiento en chunks:
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)
Implementaciones por Lenguaje
Python (ijson)
import ijson
# Stream desde archivo
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 desde respuesta 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');
});
Línea de Comandos (jq)
# Modo stream - procesa objetos individualmente
jq --stream 'select(length == 2) | .[1]' large.json
# Procesar JSON Lines
cat data.jsonl | jq -c 'select(.age > 30)'
# Convertir array a JSON Lines
jq -c '.[]' large_array.json > data.jsonl
Cuándo Usar Streaming
| Escenario | Estándar | Streaming |
|---|---|---|
| Archivo menor a 100 MB | Preferido | Excesivo |
| Archivo de 100 MB a 1 GB | Depende de la RAM | Recomendado |
| Archivo mayor a 1 GB | No viable | Requerido |
| Respuesta HTTP (grande) | Riesgo de timeout | Stream conforme se recibe |
| Feed de datos en tiempo real | No aplicable | Requerido |
| Lectura simple única | Preferido | Innecesario |
Comparación de Rendimiento
Procesando 1 millón de registros (archivo de 1 GB):
| Enfoque | Uso de Memoria | Tiempo de Procesamiento | Complejidad |
|---|---|---|---|
| json.load() | 3-5 GB | 15 seg | Simple |
| ijson streaming | 50 MB | 45 seg | Moderada |
| JSON Lines | 10 MB | 12 seg | Simple |
| Chunks (1000) | 100 MB | 20 seg | Moderada |
El streaming usa mucha menos memoria pero es más lento para el parseo estilo SAX debido a la sobrecarga del modelo basado en eventos. JSON Lines es tanto rápido como eficiente en memoria porque cada línea es una operación de parseo independiente.
Mejores Prácticas
- Usa JSON Lines cuando sea posible: Es el formato de streaming más simple y funciona con herramientas estándar.
- Configura tamaños de buffer: Configura los buffers de lectura para un rendimiento óptimo (típicamente de 64 KB a 1 MB).
- Procesa en lotes: Agrupa las inserciones en base de datos y las llamadas a API en lugar de procesar un registro a la vez.
- Maneja errores con gracia: En streaming, un registro malformado no debería bloquear todo el pipeline.
- Monitorea la memoria: Usa herramientas de profiling para verificar que el streaming realmente mantiene la memoria acotada.
Para formatear y validar archivos JSON más pequeños, nuestro Formateador JSON maneja documentos de hasta 100 MB con formateo en tiempo real.
FAQ
¿Puedo usar JSONPath con parsers de streaming?
Algunas bibliotecas de streaming soportan filtrado basado en rutas. Python ijson.items soporta filtrado por ruta durante el streaming. Sin embargo, las consultas JSONPath complejas (filtros, comodines entre niveles) típicamente requieren el documento completo en memoria. Para consultas basadas en rutas, consulta nuestra guía de JSONPath.
¿Cómo convierto un array JSON grande a JSON Lines?
Usa jq -c '.[]' input.json > output.jsonl para archivos que caben en memoria. Para archivos realmente grandes, usa un convertidor de streaming: lee el array con un parser de streaming y escribe cada elemento como una línea.
Recursos Relacionados
- Formateador JSON — Formatea y valida archivos JSON
- Guía de Consultas JSONPath — Extrae datos de JSON eficientemente
- Consejos para el Editor JSON — Trabaja con documentos JSON grandes