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

JSON-Streaming für große Dateien: Verarbeiten ohne alles zu laden

Standard-JSON-Parsing lädt das gesamte Dokument in den Speicher, baut eine vollständige Datenstruktur auf und gibt Ihnen dann Zugriff. Für eine 10-MB-Datei funktioniert das problemlos. Für eine 10-GB-Datei läuft Ihr Prozess aus dem Speicher und stürzt ab. Streaming-Parser lösen dieses Problem, indem sie JSON inkrementell verarbeiten — Daten lesen und verarbeiten, sobald sie ankommen, ohne jemals das gesamte Dokument im Speicher zu halten.

Das Problem mit Standard-Parsing

import json

# This loads the ENTIRE file into memory
with open('huge.json') as f:
    data = json.load(f)  # 10 GB file = 10+ GB of RAM

# Processing happens after full load
for item in data['records']:
    process(item)

Für eine Datei mit 1 Million Datensätzen zu je 10 KB benötigt Standard-Parsing:

  • Dateigröße: ~10 GB
  • Speicher für Parsing: ~10 GB (der rohe String)
  • Speicher für Datenstruktur: ~15-20 GB (Python-Objekte sind größer als rohes JSON)
  • Gesamt: ~25-30 GB RAM

Streaming reduziert dies auf Megabytes.

Streaming-Ansätze

SAX-Style (Ereignisbasiert)

Der Parser gibt Ereignisse aus, wenn er JSON-Token erkennt:

import ijson

# Process items one at a time - constant memory usage
with open('huge.json', 'rb') as f:
    for record in ijson.items(f, 'records.item'):
        process(record)  # Each record is parsed individually
        # Previous records are garbage collected

Ereignisse umfassen: start_map, map_key, end_map, start_array, end_array, string, number, boolean, null.

JSON Lines (JSONL / NDJSON)

Ein einfacherer Ansatz: ein JSON-Objekt pro Zeile. Jede Zeile ist ein vollständiges, gültiges JSON-Dokument:

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

Die Verarbeitung ist trivial — Zeile für Zeile lesen:

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

Vorteile von JSON Lines:

  • Jede Zeile ist unabhängig parsebar (parallele Verarbeitung)
  • Anhängefreundlich (einfach eine neue Zeile hinzufügen)
  • Funktioniert mit Standard-Unix-Tools (grep, wc, head, tail)
  • Natürlich für Logdateien und Streaming-Daten

Chunk-basierte Verarbeitung

Für Standard-JSON-Arrays die Verarbeitung in Chunks aufteilen:

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)

Sprachspezifische Implementierungen

Python (ijson)

import ijson

# Stream from file
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 from HTTP response
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');
});

Kommandozeile (jq)

# Stream mode - process objects individually
jq --stream 'select(length == 2) | .[1]' large.json

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

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

Wann Streaming verwenden

SzenarioStandardStreaming
Datei unter 100 MBBevorzugtÜbertrieben
Datei 100 MB bis 1 GBHängt vom RAM abEmpfohlen
Datei über 1 GBNicht machbarErforderlich
HTTP-Antwort (groß)Timeout-RisikoAls Stream empfangen
Echtzeit-DatenfeedNicht anwendbarErforderlich
Einfaches einmaliges LesenBevorzugtUnnötig

Leistungsvergleich

Verarbeitung von 1 Million Datensätzen (1 GB Datei):

AnsatzSpeicherverbrauchVerarbeitungszeitKomplexität
json.load()3-5 GB15 Sek.Einfach
ijson Streaming50 MB45 Sek.Mittel
JSON Lines10 MB12 Sek.Einfach
Chunk-basiert (1000)100 MB20 Sek.Mittel

Streaming verbraucht weit weniger Speicher, ist aber langsamer beim SAX-Style-Parsing aufgrund des ereignisgesteuerten Overheads. JSON Lines ist sowohl schnell als auch speichereffizient, da jede Zeile eine unabhängige Parse-Operation ist.

Best Practices

  1. JSON Lines verwenden, wenn möglich: Es ist das einfachste Streaming-Format und funktioniert mit Standard-Tools.
  2. Puffergrößen festlegen: Lesepuffer für optimalen Durchsatz konfigurieren (typischerweise 64 KB bis 1 MB).
  3. In Batches verarbeiten: Datenbank-Inserts und API-Aufrufe bündeln, anstatt einen Datensatz nach dem anderen zu verarbeiten.
  4. Fehler elegant behandeln: Beim Streaming sollte ein fehlerhafter Datensatz nicht die gesamte Pipeline zum Absturz bringen.
  5. Speicher überwachen: Profiling verwenden, um zu überprüfen, dass Streaming den Speicher tatsächlich begrenzt hält.

Für die Formatierung und Validierung kleinerer JSON-Dateien verarbeitet unser JSON Formatter Dokumente bis 100 MB mit Echtzeit-Formatierung.

FAQ

Kann ich JSONPath mit Streaming-Parsern verwenden?

Einige Streaming-Bibliotheken unterstützen pfadbasierte Filterung. Python ijson.items unterstützt das Filtern nach Pfad während des Streamings. Komplexe JSONPath-Abfragen (Filter, Wildcards über Ebenen hinweg) benötigen jedoch typischerweise das vollständige Dokument im Speicher. Für pfadbasierte Abfragen siehe unseren JSONPath-Leitfaden.

Wie konvertiere ich ein großes JSON-Array zu JSON Lines?

Verwenden Sie jq -c '.[]' input.json > output.jsonl für Dateien, die in den Speicher passen. Für wirklich große Dateien verwenden Sie einen Streaming-Konverter: Lesen Sie das Array mit einem Streaming-Parser und schreiben Sie jedes Element als Zeile.

Verwandte Ressourcen

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