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

JSON Streaming untuk File Besar: Proses Tanpa Memuat Semuanya

Parsing JSON standar memuat seluruh dokumen ke dalam memori, membangun struktur data lengkap, lalu memberi Anda akses. Untuk file 10 MB, itu berfungsi dengan baik. Untuk file 10 GB, proses Anda kehabisan memori dan crash. Parser streaming mengatasi ini dengan memproses JSON secara bertahap — membaca dan menangani data saat tiba, tanpa pernah menyimpan seluruh dokumen di memori.

Masalah dengan Parsing Standar

import json

# Ini memuat SELURUH file ke dalam memori
with open('huge.json') as f:
    data = json.load(f)  # File 10 GB = 10+ GB RAM

# Pemrosesan terjadi setelah dimuat penuh
for item in data['records']:
    process(item)

Untuk file dengan 1 juta record berukuran 10 KB masing-masing, parsing standar membutuhkan:

  • Ukuran file: ~10 GB
  • Memori untuk parsing: ~10 GB (string mentah)
  • Memori untuk struktur data: ~15-20 GB (objek Python lebih besar dari JSON mentah)
  • Total: ~25-30 GB RAM

Streaming menguranginya menjadi megabyte.

Pendekatan Streaming

Gaya SAX (Berbasis Event)

Parser mengeluarkan event saat menemukan token JSON:

import ijson

# Proses item satu per satu - penggunaan memori konstan
with open('huge.json', 'rb') as f:
    for record in ijson.items(f, 'records.item'):
        process(record)  # Setiap record di-parse secara individual
        # Record sebelumnya di-garbage collect

Event meliputi: start_map, map_key, end_map, start_array, end_array, string, number, boolean, null.

JSON Lines (JSONL / NDJSON)

Pendekatan yang lebih sederhana: satu objek JSON per baris. Setiap baris adalah dokumen JSON yang lengkap dan valid:

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

Pemrosesan sangat sederhana — baca baris per baris:

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

Keunggulan JSON Lines:

  • Setiap baris dapat di-parse secara independen (pemrosesan paralel)
  • Mudah ditambahkan (cukup tambah baris baru)
  • Berfungsi dengan alat Unix standar (grep, wc, head, tail)
  • Natural untuk file log dan data streaming

Pemrosesan Terpotong (Chunked)

Untuk array JSON standar, bagi pemrosesan menjadi chunk:

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)

Implementasi per Bahasa

Python (ijson)

import ijson

# Stream dari 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 dari respons 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');
});

Command Line (jq)

# Mode stream - proses objek satu per satu
jq --stream 'select(length == 2) | .[1]' large.json

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

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

Kapan Menggunakan Streaming

SkenarioStandarStreaming
File di bawah 100 MBDirekomendasikanBerlebihan
File 100 MB hingga 1 GBTergantung RAMDirekomendasikan
File di atas 1 GBTidak layakWajib
Respons HTTP (besar)Risiko timeoutStream saat diterima
Feed data real-timeTidak berlakuWajib
Pembacaan satu kali sederhanaDirekomendasikanTidak perlu

Perbandingan Performa

Memproses 1 juta record (file 1 GB):

PendekatanPenggunaan MemoriWaktu PemrosesanKompleksitas
json.load()3-5 GB15 detikSederhana
ijson streaming50 MB45 detikMenengah
JSON Lines10 MB12 detikSederhana
Chunked (1000)100 MB20 detikMenengah

Streaming menggunakan memori jauh lebih sedikit tetapi lebih lambat untuk parsing gaya SAX karena overhead berbasis event. JSON Lines cepat dan hemat memori karena setiap baris adalah operasi parse independen.

Praktik Terbaik

  1. Gunakan JSON Lines jika memungkinkan: Ini adalah format streaming paling sederhana dan berfungsi dengan alat standar.
  2. Atur ukuran buffer: Konfigurasikan buffer baca untuk throughput optimal (biasanya 64 KB hingga 1 MB).
  3. Proses secara batch: Batch insert database dan panggilan API daripada memproses satu record pada satu waktu.
  4. Tangani error dengan baik: Dalam streaming, satu record yang salah format seharusnya tidak menghentikan seluruh pipeline.
  5. Pantau memori: Gunakan profiling untuk memverifikasi bahwa streaming benar-benar menjaga memori tetap terbatas.

Untuk memformat dan memvalidasi file JSON yang lebih kecil, JSON Formatter kami menangani dokumen hingga 100 MB dengan pemformatan real-time.

FAQ

Bisakah saya menggunakan JSONPath dengan parser streaming?

Beberapa library streaming mendukung filtering berbasis path. Python ijson.items mendukung filtering berdasarkan path selama streaming. Namun, query JSONPath yang kompleks (filter, wildcard lintas level) biasanya memerlukan dokumen lengkap di memori. Untuk query berbasis path, lihat panduan JSONPath kami.

Bagaimana cara mengonversi array JSON besar ke JSON Lines?

Gunakan jq -c '.[]' input.json > output.jsonl untuk file yang muat di memori. Untuk file yang benar-benar besar, gunakan konverter streaming: baca array dengan parser streaming dan tulis setiap elemen sebagai satu baris.

Sumber Daya Terkait

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