Выбор формата хранения: CSV, JSON, JSONL, Avro, Parquet, ORC
В предыдущих уроках мы разбирали отдельные форматы — JSON в API, Avro в Kafka, Parquet в data lake-ах. Теперь сведём их в единую систему координат и научимся выбирать формат под конкретную задачу. Это решение влияет на стоимость хранения, скорость запросов и удобство интеграции на годы вперёд.
Зачем нужен Schema Registry: проблема бесхозных данныхШесть основных форматов
| Формат | Тип | Сжатие | Schema | Human-readable | Лучше всего для |
|---|---|---|---|---|---|
| CSV | row, текст | нет (обычно) | нет | да | ad-hoc, экспорт в Excel, малые объёмы |
| JSON | row, текст | нет | опционально (JSON Schema) | да | API responses, config файлы |
| JSONL | row, текст | нет | опционально | да | логи, потоковая обработка |
| Avro | row, бинарный | snappy/zstd | обязательная | нет | Kafka, streaming, schema evolution |
| Parquet | column, бинарный | snappy/zstd | обязательная | нет | data lake, OLAP-аналитика |
| ORC | column, бинарный | snappy/zstd | обязательная | нет | альтернатива Parquet, Hive ecosystem |
Когда выбрать какой
CSV: текстовый row-формат
id,name,age,city
1,Alice,30,Moscow
2,Bob,25,Kazan
Берите CSV, если:
- Данные нужно открыть в Excel/Google Sheets.
- Объём — мегабайты, не гигабайты.
- Получатель не имеет инфраструктуры для бинарных форматов.
- Нужна максимальная совместимость с чем угодно.
Не берите, если:
- Объём больше 1 GB (парсинг будет мучительным).
- В данных есть запятые/переносы строк (escaping ужасен).
- Нужны типы (всё парсится как строки, дата угадывается).
- Аналитика OLAP (CSV scan-ит всё подряд, не имеет statistics).
CSV не имеет строгой спецификации. RFC 4180 — слабая попытка. На практике каждый инструмент трактует quoting и escaping по-своему. Готовьтесь к боли с UTF-8 BOM, CRLF против LF, разделителями (, vs ; в немецких локалях).
JSON: текстовый row-формат с типами
{"id": 1, "name": "Alice", "age": 30, "tags": ["vip", "early"]}
Берите JSON, если:
- Это API response (стандарт де-факто для REST/GraphQL).
- Конфиг приложения.
- Структура иерархическая, со вложенными объектами.
Не берите, если:
- Вам нужен потоковый формат (целый файл — массив объектов, парсер должен прочитать всё в память). Используйте JSONL.
- Объём терабайтный (overhead имён полей огромный).
- Аналитические запросы (никакого pruning, только sequential scan).
JSON-парсинг медленнее, чем кажется: на 1 GB файле — десятки секунд. Используйте orjson или simdjson для ускорения в 5-10 раз.
JSONL (NDJSON): line-delimited JSON
{"id": 1, "name": "Alice"}
{"id": 2, "name": "Bob"}
{"id": 3, "name": "Carol"}
Один JSON-объект на строку. Решает главную проблему JSON: можно читать построчно, не загружая весь файл.
Берите JSONL, если:
- Логи приложения.
- Append-only event log.
- Промежуточный формат в pipeline между ingestion и сериализацией в Parquet.
- Стриминг ответов больших API (jsonlines в качестве response-формата).
Не берите, если:
- Нужна аналитика — конвертируйте в Parquet.
- Есть alternative с сжатием/типами.
JSONL — стандарт MLOps (датасеты в Hugging Face, OpenAI fine-tuning), наблюдаемости (Grafana Loki хранит логи в JSONL-подобном формате), стриминга API (LLM-провайдеры иногда отдают так).
Avro: бинарный row-формат
Берите Avro, если:
- Это Kafka-сообщения через Schema Registry.
- Долгосрочное хранение со schema evolution.
- Streaming pipeline с inter-service контрактами.
- Hadoop/HDFS экосистема.
Не берите, если:
- Данные нужно анализировать аналитически (Avro scan-ит всё, нет column pruning).
- Нет инфраструктуры для управления schema (Registry, codegen).
- Получатель не понимает Avro (большинство BI-инструментов — нет).
Parquet: бинарный column-формат
Берите Parquet, если:
- Data lake / data warehouse layer.
- Аналитические запросы (OLAP, агрегации).
- Iceberg / Delta Lake / Hudi (все используют Parquet под капотом).
- Storage tier для batch ML training.
Не берите, если:
- Streaming (нельзя писать одну запись).
- Точечные обновления существующих строк (нужен формат с updates: Iceberg/Delta поверх).
- Малые объёмы (overhead row groups vs CSV).
ORC: альтернатива Parquet
ORC (Optimized Row Columnar) — формат от Hortonworks, часть Hive ecosystem. Концептуально похож на Parquet: tripple-уровневая структура, columnar storage, encoding + compression. Отличается deталями:
- Stripes вместо row groups.
- Light-weight indexes на каждые 10000 строк (быстрее skipping).
- Лучше для очень wide tables (1000+ колонок).
- Лучшая интеграция с Hive ACID транзакциями.
Берите ORC, если:
- Вы в Hive/HDFS-стеке.
- Нужны ACID-транзакции в Hive.
Берите Parquet, если:
- Вы вне строгого Hive-окружения (S3 + Spark, Snowflake, ClickHouse, DuckDB).
В 2026 Parquet — выигравший стандарт за пределами Hive. ORC сохраняется в Hive-инсталляциях как наследие.
Бенчмарк: 10 миллионов строк продаж
Возьмём типовую DE-таблицу: order_id, user_id, product_id, amount, currency, ts, country, device, status. 9 колонок, 10M строк.
| Формат | Размер на диске | Время записи | Время чтения всех строк | Время SELECT amount, country WHERE country = 'US' |
|---|---|---|---|---|
| CSV (gzip) | 320 MB | 18 s | 25 s | 25 s (full scan) |
| JSON | 2.1 GB | 35 s | 60 s | 60 s |
| JSONL | 1.9 GB | 25 s | 45 s | 45 s |
| JSONL + gzip | 280 MB | 30 s | 30 s | 30 s |
| Avro (snappy) | 145 MB | 8 s | 12 s | 12 s (no pruning) |
| Parquet (snappy) | 95 MB | 10 s | 8 s | 1.2 s (column + predicate pushdown) |
| Parquet (zstd) | 75 MB | 11 s | 9 s | 1.4 s |
| ORC (zstd) | 80 MB | 12 s | 9 s | 1.3 s |
Цифры — примерные, зависят от данных и железа. Основной вывод: Parquet выигрывает у CSV в 20+ раз на аналитических запросах и в 3 раза на размере. Avro проигрывает Parquet на аналитике, но идеально подходит для Kafka, где нужны row-by-row сообщения.
Какая природа данных?
Стартовая точка любого выбора форматаAvro в Kafka + Schema Registry
Каждое сообщение независимо, append-only потокАналитика SQL поверх big data?
Большие объёмы, аналитикаParquet с партиционированием
Columnar + statistics + pruning = огромный выигрыш на OLAPЛоги / append-only event log?
Логи, события приложенияJSONL.gz, потом конвертация в Parquet
Простой формат, можно стримить, gzip снижает размер в 5-10 разПередача внешнему получателю без Big Data tooling-а?
Надо отправить data scientist-у в виде, понятном ExcelCSV или JSON
Самый универсальный формат, открывается в Excel, Notepad, PythonАнтипаттерны
Parquet для row-by-row updates
«Обновим в Parquet строку, где user_id=42». Это не работает: Parquet immutable, нужно перезаписать целый row group. Правильно: использовать слой поверх (Iceberg, Delta Lake, Hudi), который умеет в logical updates через файлы изменений + compaction.
JSON для аналитики 1 TB
Парсинг 1 TB JSON — десятки часов CPU. Storage будет 5-10x больше эквивалентного Parquet. Аналитика — full scan на каждом запросе. Решение: один раз конвертировать в Parquet, затем работать с ним.
CSV для сложных типов
Вложенные объекты, массивы, NULL vs пустая строка, даты в разных форматах — CSV не способна это нормально передать. Получатель будет угадывать, и ошибётся. Используйте JSONL или Parquet.
Avro для аналитики
Avro — row-формат. На запросах с фильтрами и проекциями он читает все колонки и все записи. Это в 10-50 раз медленнее Parquet. Используйте Avro только для transport/streaming, для аналитики — конвертируйте в Parquet.
Parquet для маленьких частых файлов
«Я пишу события по одному в S3 как Parquet». В итоге миллионы маленьких файлов, footer overhead больше payload-а, query engine задыхается на listing-е. Правильно: батчить — собирать в Avro/JSONL, потом раз в час/день конвертировать в Parquet.
Custom формат
«Мы изобрели свой бинарный формат». Через год: никакой инструмент его не поддерживает, документация устарела, разработчик уволился. Используйте стандарты — выгода от specialty форматов почти никогда не окупает потерю экосистемы.
Самый частый антипаттерн в DE — оставлять данные в JSON «потому что так пришло». Конвертация в Parquet занимает один раз 10 минут и экономит часы на каждом будущем запросе. Делайте её сразу в bronze->silver слое pipeline-а.
Гибридный подход в pipeline
Реальный DE pipeline использует разные форматы на разных уровнях:
1. Ingestion -> JSONL.gz (простота, append, small files OK)
2. Bronze layer -> Parquet (raw, partitioned by ingestion date)
3. Silver layer -> Parquet (cleaned, partitioned by business date)
4. Gold layer -> Parquet (aggregated, partitioned by query pattern)
5. Serving -> Parquet + materialized views в OLAP-движке
6. Streaming -> Avro в Kafka между сервисами
7. API responses -> JSON (REST), Protobuf (gRPC)
8. Reports -> CSV для бизнеса, Parquet для DS
Каждый формат на своём месте. Не пытайтесь использовать один формат везде — это всегда компромисс не в вашу пользу.
Memo для junior DE
Что нужно уметь практически:
- Конвертировать между форматами через pyarrow и pandas:
pd.read_csv().to_parquet(),pd.read_json(lines=True).to_parquet(). - Понимать сжатие: snappy быстро, zstd компактнее, gzip универсально, brotli редко.
- Уметь партиционировать Parquet по дате/региону через
partition_cols. - Различать сценарии: streaming = Avro, analytics = Parquet, передача наружу = CSV/JSON.
- Не выбирать формат «навсегда» — на разных слоях pipeline-а оптимальны разные форматы.