Конвертировать датасет из JSONL в Parquet

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Конвертировать датасет из JSONL в Parquet

1. Цель задачи

Научиться эффективно конвертировать большие датасеты из формата JSONL (JSON Lines) в колоночный формат Parquet с использованием библиотек Pandas и Dask. Сравнить размер файлов на диске и скорость чтения данных из каждого формата, чтобы убедиться в преимуществах Parquet для аналитических нагрузок.

Ключевой результат Получить Parquet-файл, который в 5–10 раз меньше исходного JSONL и читается в 3 и более раз быстрее при выполнении типовых операций (фильтрация, агрегация).

2. Исходные данные

Что нужноОткуда взять
Датасет в формате JSONL (минимум 500 000 строк, 10–20 колонок разных типов)Сгенерировать синтетически (см. ниже) или скачать публичный (например, amazon_reviews, wikipedia в JSONL)
Python 3.10+Установить из официального дистрибутива
Библиотеки: pandas, dask, pyarrow, fastparquet, numpy, psutilpip install pandas dask[complete] pyarrow fastparquet numpy psutil
Скрипты для бенчмаркингаНаписать самостоятельно в рамках задачи

Если нет реального инструмента — симулируем:

  1. Сгенерировать dataset|синтетический датасет с помощью Python:
    • 1 000 000 строк
    • Колонки: id (int), timestamp (datetime), category (str, 20 уникальных), value (float), description (str, 100–200 символов), flag (bool), nested (JSON-объект с 3 полями)
    • Сохранить в файл dataset.jsonl (каждая строка — валидный JSON)
  2. Убедиться, что размер файла не менее 200 МБ (при необходимости увеличить количество строк или длину строковых полей).

3. Технологический стек

КомпонентИнструментыНазначение
Язык программированияPython 3.10+Основной язык для скриптов
Обработка данных (однопоточная)Pandas 2.xЗагрузка JSONL, конвертация в Parquet, чтение
Обработка данных (распределённая)Dask 2024.xПотоковая/чанковая конвертация для больших данных
Формат ParquetPyArrow / fastparquetЗапись и чтение Parquet-файлов
Генерация данныхNumPy, random, jsonСоздание синтетического датасета
Измерение времени и памятиtimeit, psutil, os.path.getsizeБенчмаркинг

4. Этапы выполнения

Этап 1: Подготовка данных и окружения (30 минут)

Действия

  1. Установить все необходимые библиотеки (см. раздел 2).
  2. Создать скрипт generate_dataset.py для генерации синтетического датасета:
    • Использовать numpy.random и random для заполнения колонок.
    • Каждая строка — словарь, сериализованный в JSON.
    • Записать в файл dataset.jsonl построчно.
    • Вывести итоговый размер файла и количество строк.
  3. Убедиться, что файл создан и доступен для чтения.

Пример кода генерации (фрагмент):

import json
import numpy as np
from datetime import datetime, timedelta

N = 1_000_000
categories = ['A', 'B', 'C', 'D'] * 5
start_date = datetime(2020, 1, 1)

with open('dataset.jsonl', 'w') as f:
    for i in range(N):
        row = {
            'id': i,
            'timestamp': (start_date + timedelta(seconds=i)).isoformat(),
            'category': np.random.choice(categories),
            'value': round(np.random.uniform(0, 1000), 2),
            'description': ' '.join(np.random.choice(['word']*10, size=20)),
            'flag': bool(np.random.randint(0, 2)),
            'nested': {'a': i % 10, 'b': np.random.rand(), 'c': 'x' * 5}
        }
        f.write(json.dumps(row) + '\n')

Ожидаемый результат этапа Файл dataset.jsonl размером >200 МБ, готовый к конвертации.

Этап 2: Конвертация с помощью Pandas (1 час)

Действия

  1. Написать скрипт convert_pandas.py:
    • Загрузить JSONL в DataFrame с помощью pd.read_json('dataset.jsonl', lines=True).
    • Замерить время загрузки и объём памяти (через psutil).
    • Сохранить DataFrame в Parquet: df.to_parquet('dataset_pandas.parquet', index=False, engine='pyarrow').
    • Замерить время записи.
    • Вывести размеры исходного и результирующего файлов.
  2. Прочитать Parquet обратно: pd.read_parquet('dataset_pandas.parquet') — замерить время.
  3. Выполнить простую агрегацию (например, среднее value по category) на JSONL (через загрузку) и на Parquet — сравнить время выполнения.

Ожидаемый результат этапа Parquet-файл создан, получены первые метрики размера и скорости.

Этап 3: Конвертация с помощью Dask (1 час)

Действия

  1. Написать скрипт convert_dask.py:
    • Загрузить JSONL через dask.dataframe.read_json('dataset.jsonl', lines=True, blocksize=50e6) (чанки по 50 МБ).
    • Сохранить в Parquet: df.to_parquet('dataset_dask.parquet', engine='pyarrow').
    • Замерить общее время (Dask выполняет ленивые вычисления, нужен .compute() для триггера).
  2. Прочитать Parquet через Dask и выполнить ту же агрегацию, что и в этапе 2.
  3. Сравнить время выполнения с Pandas-версией.

Ожидаемый результат этапа Второй Parquet-файл (или единый, если Dask слил в один), метрики для Dask.

Этап 4: Бенчмаркинг и сравнение (1 час)

Действия

  1. Написать скрипт benchmark.py, который выполняет следующие замеры для каждого формата (JSONL, Pandas-Parquet, Dask-Parquet):
    • Размер файла на диске (байты).
    • Время полного чтения (load).
    • Время выполнения агрегации groupby('category')['value'].mean().
    • Пиковое потребление памяти (через psutil.Process().memory_info().rss).
  2. Повторить каждый замер 3 раза, взять среднее.
  3. Вывести таблицу сравнения:
ФорматРазмер (МБ)Время чтения (с)Время агрегации (с)Память (МБ)
JSONL............
Pandas Parquet............
Dask Parquet............
  1. Рассчитать коэффициенты: сжатие (размер JSONL / размер Parquet) и ускорение (время JSONL / время Parquet).

Ожидаемый результат этапа Численные метрики, подтверждающие или опровергающие целевые показатели (5–10x сжатие, 3x+ ускорение).

Этап 5: Анализ и документирование (30 минут)

Действия

  1. Написать краткий отчёт report.md, включающий:
    • Описание датасета и окружения.
    • Таблицу с результатами бенчмарка.
    • Выводы: достигнуты ли целевые показатели, какой подход (Pandas vs Dask) оказался эффективнее для данного объёма данных.
    • Рекомендации по выбору формата для production-сценариев.
  2. Зафиксировать все скрипты и файлы в репозитории (опционально).

Ожидаемый результат этапа Файл report.md с анализом, готовый к ревью.

5. Критерии приемки (Definition of Done)

  • Сгенерирован датасет в JSONL размером не менее 200 МБ.
  • Parquet-файл создан с помощью Pandas и Dask (два отдельных файла или один, если Dask слил).
  • Размер Parquet-файла меньше размера JSONL минимум в 5 раз.
  • Время чтения Parquet (любым способом) меньше времени чтения JSONL минимум в 3 раза.
  • Время агрегации на Parquet меньше времени агрегации на JSONL минимум в 3 раза.
  • Все замеры выполнены не менее 3 раз, результаты усреднены.
  • Написан отчёт report.md с таблицей и выводами.
  • Код скриптов воспроизводим (зафиксированы версии библиотек в requirements.txt).

6. Ожидаемый результат

Основной артефакт Репозиторий (или папка) со следующими файлами:

  • dataset.jsonl — исходный датасет.
  • dataset_pandas.parquet — результат конвертации через Pandas.
  • dataset_dask.parquet — результат конвертации через Dask (может быть директорией с партициями).
  • generate_dataset.py — скрипт генерации.
  • convert_pandas.py — скрипт конвертации Pandas.
  • convert_dask.py — скрипт конвертации Dask.
  • benchmark.py — скрипт бенчмаркинга.
  • report.md — отчёт с результатами.
  • requirements.txt — список зависимостей.

Дополнительно (опционально): Графики зависимости времени чтения от размера данных (если сделать несколько датасетов разного объёма).

7. Возможные сложности и их решение

СложностьРешение
Нехватка оперативной памяти при загрузке большого JSONL в PandasИспользовать Dask с чанками или читать JSONL построчно через json.loads и накапливать в список с последующим pd.DataFrame (но это медленно). Либо уменьшить размер датасета до 500k строк.
Разные версии Parquet (pyarrow vs fastparquet) дают разный размерИспользовать один движок (pyarrow) для всех экспериментов.
Dask создаёт несколько файлов-партицийЗамерить суммарный размер всех партиций. Для чтения использовать dask.dataframe.read_parquet — он автоматически объединит.
Время записи Parquet может быть больше, чем время записи JSONLЭто ожидаемо, цель — ускорение чтения, а не записи. Замерить отдельно.
Шум в замерах из-за фоновых процессовВыполнять замеры на «холодную» (сбросить кэш ОС) или повторять 3+ раз и брать медиану.

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Подготовка данных и окружения30 мин
Этап 2: Конвертация с Pandas1 ч
Этап 3: Конвертация с Dask1 ч
Этап 4: Бенчмаркинг и сравнение1 ч
Этап 5: Анализ и документирование30 мин
Итого4 ч

Примечание Для первого выполнения задачи рекомендуется заложить до 6 часов с учётом отладки и изучения документации.

9. Связанные вопросы из базы знаний

ВопросТема
42Форматы хранения данных: JSON vs Parquet vs Avro
87Оптимизация чтения больших файлов в Pandas
134Dask: отложенные вычисления и чанкование
211Сжатие в Parquet: Snappy, Gzip, Zstd
298Бенчмаркинг производительности I/O в Python
356Работа с JSONL: потоковая загрузка
412Колоночные форматы vs строчные: когда что использовать
489Управление памятью при обработке больших датасетов
567PyArrow vs fastparquet: сравнение движков
634Практика: конвертация логов из JSON в Parquet

10. Чек-лист самопроверки

  • Я сгенерировал датасет и убедился, что он корректен (валидный JSON, нужное количество строк).
  • Я выполнил конвертацию через Pandas и Dask, получив Parquet-файлы.
  • Я написал скрипт бенчмарка, который измеряет размер, время чтения и агрегации, и память.
  • Я проверил, что Parquet действительно меньше JSONL в 5+ раз и читается быстрее в 3+ раза.
  • Я задокументировал все шаги и результаты в report.md, включая таблицу и выводы.