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

Потоковая обработка JSON для больших файлов: обработка без полной загрузки

Стандартный парсинг JSON загружает весь документ в память, строит полную структуру данных и только потом предоставляет доступ. Для файла в 10 МБ это работает нормально. Для файла в 10 ГБ ваш процесс исчерпает память и упадёт. Потоковые парсеры решают эту проблему, обрабатывая JSON инкрементально — читая и обрабатывая данные по мере поступления, никогда не удерживая весь документ в памяти.

Проблема стандартного парсинга

import json

# Это загружает ВЕСЬ файл в память
with open('huge.json') as f:
    data = json.load(f)  # 10 ГБ файл = 10+ ГБ RAM

# Обработка начинается только после полной загрузки
for item in data['records']:
    process(item)

Для файла с 1 миллионом записей по 10 КБ каждая стандартный парсинг требует:

  • Размер файла: ~10 ГБ
  • Память для парсинга: ~10 ГБ (необработанная строка)
  • Память для структуры данных: ~15–20 ГБ (объекты Python больше, чем необработанный JSON)
  • Итого: ~25–30 ГБ RAM

Потоковая обработка сводит это к мегабайтам.

Подходы к потоковой обработке

SAX-стиль (на основе событий)

Парсер генерирует события при обнаружении токенов JSON:

import ijson

# Обработка элементов по одному — постоянное потребление памяти
with open('huge.json', 'rb') as f:
    for record in ijson.items(f, 'records.item'):
        process(record)  # Каждая запись парсится индивидуально
        # Предыдущие записи очищаются сборщиком мусора

Типы событий: start_map, map_key, end_map, start_array, end_array, string, number, boolean, null.

JSON Lines (JSONL / NDJSON)

Более простой подход: один объект JSON на строку. Каждая строка — полный, валидный JSON-документ:

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

Обработка тривиальна — читайте построчно:

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

Преимущества JSON Lines:

  • Каждая строка разбирается независимо (параллельная обработка)
  • Удобен для добавления (просто добавьте новую строку)
  • Работает со стандартными инструментами Unix (grep, wc, head, tail)
  • Естественен для лог-файлов и потоковых данных

Обработка порциями

Для стандартных JSON-массивов можно разделить обработку на порции:

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)

Реализации для разных языков

Python (ijson)

import ijson

# Потоковое чтение из файла
with open('large.json', 'rb') as f:
    parser = ijson.parse(f)
    for prefix, event, value in parser:
        if prefix == 'records.item.name':
            print(value)

# Потоковое чтение из 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');
});

Командная строка (jq)

# Потоковый режим — обработка объектов по отдельности
jq --stream 'select(length == 2) | .[1]' large.json

# Обработка JSON Lines
cat data.jsonl | jq -c 'select(.age > 30)'

# Преобразование массива в JSON Lines
jq -c '.[]' large_array.json > data.jsonl

Когда использовать потоковую обработку

СценарийСтандартныйПотоковый
Файл до 100 МБПредпочтительноИзбыточно
Файл 100 МБ – 1 ГБЗависит от RAMРекомендуется
Файл более 1 ГБНереализуемоОбязательно
HTTP-ответ (большой)Риск тайм-аутаПотоковое получение
Поток данных в реальном времениНеприменимоОбязательно
Простое однократное чтениеПредпочтительноИзлишне

Сравнение производительности

Обработка 1 миллиона записей (файл 1 ГБ):

ПодходИспользование памятиВремя обработкиСложность
json.load()3–5 ГБ15 секПростая
ijson потоковая50 МБ45 секУмеренная
JSON Lines10 МБ12 секПростая
Порциями (1000)100 МБ20 секУмеренная

Потоковая обработка использует значительно меньше памяти, но медленнее для SAX-стиля парсинга из-за накладных расходов на обработку событий. JSON Lines — одновременно быстрый и экономный по памяти, поскольку каждая строка — независимая операция парсинга.

Лучшие практики

  1. Используйте JSON Lines, когда это возможно: Это самый простой формат потоковой передачи, работающий со стандартными инструментами.
  2. Настраивайте размеры буферов: Конфигурируйте буферы чтения для оптимальной пропускной способности (обычно от 64 КБ до 1 МБ).
  3. Обрабатывайте пакетами: Группируйте вставки в базу данных и вызовы API, а не обрабатывайте по одной записи за раз.
  4. Обрабатывайте ошибки корректно: При потоковой обработке одна некорректная запись не должна обрушивать весь конвейер.
  5. Мониторьте память: Используйте профилирование, чтобы убедиться, что потоковая обработка действительно ограничивает потребление памяти.

Для форматирования и валидации файлов JSON меньшего размера наш JSON Formatter обрабатывает документы до 100 МБ с форматированием в реальном времени.

Часто задаваемые вопросы

Можно ли использовать JSONPath с потоковыми парсерами?

Некоторые потоковые библиотеки поддерживают фильтрацию по пути. Python ijson.items поддерживает фильтрацию по пути во время потоковой обработки. Однако сложные запросы JSONPath (фильтры, подстановочные символы по уровням) обычно требуют наличия всего документа в памяти. О запросах по пути читайте в нашем руководстве по JSONPath.

Как преобразовать большой JSON-массив в JSON Lines?

Используйте jq -c '.[]' input.json > output.jsonl для файлов, помещающихся в память. Для действительно больших файлов используйте потоковый конвертер: читайте массив потоковым парсером и записывайте каждый элемент как строку.

Связанные ресурсы

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