Зачем три формата
В предыдущем уроке мы поняли разницу между row-based и columnar layout. Теперь посмотрим на три конкретных формата, которые data engineer встречает чаще всего: Parquet, Avro и ORC. Все три появились в 2009-2013, и в 2026 они занимают разные ниши в data pipeline.
Цель урока — обзор: где какой формат уместен, что у него внутри на верхнем уровне, как они сочетаются в типичном пайплайне. Глубже устройство, бенчмарки и edge-cases разбираем в отдельном курсе storage-formats.
Parquet: стандарт колоночного хранения
Apache Parquet родился в 2013 году внутри Twitter и Cloudera как ответ на проблему “Hadoop хранит всё в CSV и это нерабочее”. Идея — взять колоночные принципы из прошлого урока, обернуть в формат, дружелюбный к distributed storage (HDFS, S3), и сделать стандартом.
В 2026 году Parquet — это default-формат для data lake и lakehouse: Spark, Snowflake (внутреннее представление), Databricks, BigQuery (external tables), DuckDB, Athena, ClickHouse, Iceberg, Delta Lake — все читают и пишут Parquet. Если в индустрии есть один формат, который data engineer должен знать на верхнем уровне, это Parquet.
Иерархия: File, Row Group, Column Chunk, Page
Parquet-файл имеет четырёхуровневую иерархию.
Файл делится на row groups, row group — на column chunks, column chunk — на pages
Row Group — горизонтальное разбиение таблицы. Если строк 100 млн, и row group настроен на 1 млн — будет 100 row groups. Каждый — самостоятельная единица для параллельной обработки и для predicate pushdown.
Column Chunk — данные одной колонки внутри одного row group. Внутри chunk данные сжаты и closely-packed.
Page — самый низкий уровень. Внутри column chunk данные нарезаются на pages по примерно 1 MB. Page — это что reader физически читает и декодирует за раз.
Schema, encoding, compression
Parquet несёт схему — типы каждой колонки прописаны в footer файла. Reader не угадывает типы, как в CSV.
Внутри column chunk данные сжимаются в два этапа. Сначала encoding (преобразование значений в более компактный вид):
- Dictionary encoding — словарь для низкокардинальных колонок (currency, country).
- RLE (Run-length encoding) — сжатие повторов: пять USD подряд хранятся как (USD, 5).
- Delta encoding — для возрастающих чисел (timestamp, ID).
- Bit packing — упаковка маленьких чисел в минимальное число бит.
Поверх encoding — общая компрессия: ZSTD (современный default), Snappy (старый default, всё ещё распространён), GZIP (legacy). В 2024+ индустрия конвергировалась на ZSTD: сжимает на 25-40% лучше Snappy при сопоставимой скорости decompression.
Statistics и predicate pushdown
В footer каждого row group хранятся min/max statistics по каждой колонке. Это позволяет readeru пропускать целые row groups без чтения данных. Запрос с фильтром на amount больше 1000: row group с max=500 пропускается. Это predicate pushdown, мы его разбирали в прошлом уроке.
Дополнительно с Parquet 2.5+ есть опциональные bloom filters для точечных поисков на high-cardinality колонках (user_id, transaction_id).
Partitioning
Parquet-файлы редко лежат “плоско” в одной папке. В data lake используется partitioning — иерархия папок по значению колонки:
s3://lake/orders/
dt=2026-05-15/data-001.parquet
dt=2026-05-16/data-001.parquet
dt=2026-05-17/data-001.parquet
Hive-style partitioning понимается всеми движками. Запрос с фильтром по dt сразу пропускает все папки, кроме нужной — partition pruning. Это уровень над predicate pushdown, ещё более грубый и эффективный.
Avro: row-based для streaming
Apache Avro появился в 2009 году внутри Hadoop как формат для сериализации данных между системами. Главная задача — не хранение в data lake, а обмен между процессами: Kafka-сообщения, Hadoop RPC, дампы для бэкапов.
Avro row-based: одна запись лежит целиком, потом следующая. Это делает Avro плохим для аналитики (для агрегации читается всё), но отличным для streaming: Kafka consumer получает по сообщению, парсит целиком, обрабатывает.
Avro-файл состоит из header с JSON-схемой и блоков с записями, разделённых sync markers (16-байтные случайные последовательности для синхронизации при split). Сама схема — это JSON:
{
"type": "record",
"name": "Order",
"fields": [
{"name": "order_id", "type": "long"},
{"name": "amount", "type": "double"},
{"name": "currency", "type": "string"}
]
}
Сами данные лежат в бинарном виде. Без схемы прочитать невозможно.
Schema evolution — главная фича
Уникальная особенность Avro — поддержка эволюции схемы. Представь: ты пишешь Avro полгода со схемой v1, потом добавляешь поле discount. Это schema v2. Что делать со старыми данными?
Avro решает: при чтении используются writer schema (с которой данные были записаны) и reader schema (которой данные читают). Если они отличаются, выполняется compatibility resolution:
- Если в reader schema есть поле с default value, которого нет в writer schema — используется default.
- Если в writer schema есть поле, которого нет в reader schema — поле игнорируется.
- Если типы совместимы (int в long, float в double) — автоматическая конвертация.
Это даёт возможность развивать схему без переписывания старых данных. Старый Kafka-consumer на v1 продолжает работать с v2-сообщениями (если изменения backward-compatible).
Где Avro живёт
- Kafka сообщения. Confluent Schema Registry хранит Avro-схемы по версиям, producer/consumer договариваются о версии. Стандарт enterprise-Kafka.
- Database CDC. Debezium часто пишет CDC-события в Avro в Kafka.
- Hadoop ingestion. Sqoop экспортирует RDBMS в Avro.
Avro для долгосрочного хранения аналитики не подходит — row-based проигрывает Parquet в скане в десятки раз. Но как transport в data pipeline — идеален.
ORC: альтернатива в мире Hive
Apache ORC (Optimized Row Columnar) появился в 2013 году внутри Hive как ответ на проблемы предыдущего формата RCFile. Это колоночный формат, концептуально близкий к Parquet, но с другими внутренними решениями.
ORC-файл делится на stripes (аналог row group в Parquet, обычно 64-256 MB). Stripe содержит index data (min/max, bloom filters, position pointers для каждых 10000 строк), row data (колоночные данные) и stripe footer (метаданные). В конце файла — file footer со схемой всего файла.
ORC чуть быстрее на точечных скиппинговых запросах благодаря row index по 10K строк (более тонкая гранулярность skipping). Parquet — на широких сканах. Разница в реальных бенчмарках 10-20%, не порядки.
ORC — формат Hadoop/Hive экосистемы. Если ты работаешь в компании со стэком Hive/Tez/Presto-Trino, скорее всего основной формат — ORC. Если стэк Spark/Databricks/Snowflake — почти всегда Parquet.
В 2026 году ORC заметно теряет долю: новые проекты строятся на Parquet, потому что Iceberg/Delta Lake поддерживают преимущественно Parquet. Но в legacy banking, telecom, retail компаниях ORC всё ещё стандарт.
Decision matrix: что выбирать
Карта решений: от exchange до archive
Краткая шпаргалка:
| Сценарий | Формат | Почему |
|---|---|---|
| Обмен с партнёром (МБ-ГБ) | CSV | Универсально, любая система читает |
| REST API ответ | JSON | Естественно для web |
| Лог одного сервиса | JSONL | Stream-friendly, простой парсинг |
| Kafka topic | Avro | Schema evolution, типизированный |
| Data lake (S3) | Parquet | Columnar, compression, pushdown |
| Hive table (legacy) | ORC или Parquet | Зависит от стэка |
| DWH ingestion | Parquet | Быстрый COPY с типами |
| Archive (cold) | Parquet + ZSTD | Максимальная компрессия |
| ML training | Parquet | Стандарт DataLoader |
Главное правило: текстовые форматы (CSV/JSON) на границах, бинарные колоночные (Parquet) в середине, row-based бинарные (Avro) в transport.
Типичный pipeline
Пример реального data pipeline в e-commerce, где все три формата играют свою роль:
[OLTP Postgres] -> [Debezium CDC] -> [Kafka: Avro] -> [Spark Streaming]
|
v
[S3 Data Lake: Parquet, partitioned by date]
|
v
[DWH: Snowflake / BigQuery]
- На входе: Avro в Kafka (schema evolution для эволюции схемы заказов).
- В лейке: Parquet (compressed, partitioned, optimized for analytics).
- В DWH: native columnar storage Snowflake/BigQuery (формат не виден пользователю).
ORC в этом пайплайне обычно нет, если только это не Hive-based стэк.
Debezium: как CDC захватывает изменения из Postgres Avro в Kafka: Schema Registry и бинарная сериализация Spark Structured Streaming: читаем Kafka и пишем ParquetРаспространённый антипаттерн — хранить аналитические данные в Avro “потому что есть schema evolution”. Avro не оптимизирован для аналитики (row-based, нет projection/predicate pushdown). Schema evolution есть и в Parquet через table formats (Iceberg, Delta Lake), причём в более гибком виде на уровне таблицы.
Table formats поверх Parquet
В 2026 году одиночный Parquet редко используется как production storage — поверх живут table formats: Apache Iceberg, Delta Lake, Apache Hudi. Они добавляют:
- ACID транзакции.
- Schema evolution на уровне таблицы (не файла).
- Time travel (запросы к данным “как было N часов назад”).
- Partition evolution.
- Branching (Iceberg).
Это следующая ступень после Parquet. Эту тему подробно разбираем в модуле 14 (data lakes и lakehouse).
Попробуй сам
- Возьми датасет 1-10 GB. Сохрани в CSV, JSON, JSONL, Parquet (Snappy), Parquet (ZSTD), Avro. Сравни размеры. Замерь время чтения каждого формата в pandas/DuckDB.
- Открой Parquet через
pyarrow.parquet.ParquetFileи выведи schema, num_row_groups, размеры column chunks. Посмотри, какие encoding-и Parquet выбрал для каждой колонки. - Поэкспериментируй со schema evolution Avro: создай v1 со схемой (id, amount), добавь поле currency с default. Прочитай v1-данные с v2-схемой.
- Найди в открытом dbt-проекте или Databricks-ноутбуке pipeline. Определи, какие форматы где живут — transport, storage, ingestion.
Деталь по форматам — углубление в нашем курсе storage-formats.