Контейнерный формат
Object Container File
Apache Avro определяет два уровня: формат сериализации (encoding отдельных значений) и контейнерный формат (как записи группируются в файл). Object Container File (OCF) — стандартный контейнер для хранения Avro-записей вместе со схемой.
Ключевое отличие от Parquet и ORC: метаданные Avro находятся в начале файла, а не в конце. Ридер последовательно читает header, затем data blocks — без seek к концу. Это делает формат пригодным для потоковой обработки: можно начать десериализацию, не зная размера файла.
Magic: Obj + 0x01 (4 байта)
4 байта: 0x4F 0x62 0x6A 0x01 — три ASCII-символа 'Obj' + байт версии 0x01. Идентификатор формата Avro OCF.Sync Marker (16 random bytes)
16 случайных байт, уникальных для каждого файла. Генерируются при создании. Разделитель блоков и маркер целостности данных.Parquet и ORC читают файл с конца — сначала footer, потом данные. Avro читает с начала — сначала header со схемой, потом последовательно блоки. Это принципиальная архитектурная разница: Avro оптимизирован для потоковой записи и чтения, а Parquet/ORC — для random access к колонкам.
Magic Bytes
Первые 4 байта файла — сигнатура формата:
| Байт | Hex | Символ | Значение |
|---|---|---|---|
| 0 | 0x4F | O | — |
| 1 | 0x62 | b | — |
| 2 | 0x6A | j | — |
| 3 | 0x01 | — | Версия формата (1) |
Три символа Obj + байт версии. Текущая и единственная версия — 0x01. Для сравнения: Parquet использует PAR1 (4 ASCII-символа), ORC — ORC (3 символа без байта версии).
Байт 0x01 — не ASCII-символ, а числовое значение. Если вы откроете Avro-файл в текстовом редакторе, увидите Obj + невидимый символ (SOH — Start of Heading в ASCII-таблице). В hex-дампе: 4F 62 6A 01.
File Metadata
Сразу после magic bytes следует file metadata — Avro map<string, bytes>, сериализованная в бинарном формате Avro (block encoding для map, подробнее в уроке 03). Два ключа обязательны:
Ключ avro.schema содержит полную JSON-схему всех записей в файле. Это принципиальное решение Avro: схема путешествует с данными. В Parquet схема хранится в Thrift-сериализованном footer, в ORC — в Protobuf-сериализованном footer. Avro хранит JSON-строку прямо в header.
Пример значения avro.schema:
{
"type": "record",
"name": "User",
"namespace": "com.example",
"fields": [
{"name": "id", "type": "long"},
{"name": "name", "type": "string"},
{"name": "email", "type": ["null", "string"], "default": null}
]
}
Метаданные сериализуются как Avro map в бинарном формате — не как JSON. Чтобы прочитать metadata map, ридер уже должен уметь декодировать базовые Avro-типы: long (zigzag variable-length), string (length + UTF-8), bytes (length + raw bytes). Парсинг header — это bootstrapping: минимальный Avro-декодер нужен ещё до чтения схемы.
Sync Marker
После metadata map записываются 16 байт sync marker — случайное значение, уникальное для каждого файла. Sync marker выполняет три функции:
- Разделитель блоков — маркирует конец каждого data block
- Восстановление позиции — позволяет найти начало следующего блока при повреждении данных
- Защита от конкатенации — если два файла склеить
cat a.avro b.avro, ридер обнаружит смену sync marker
Sync marker генерируется один раз при создании файла и повторяется после каждого data block. 16 байт (128 бит) дают пространство значений 2^128 — вероятность ложного совпадения со случайными данными пренебрежимо мала.
Sync marker в Avro аналогичен magic number в начале каждого column page в Parquet, но работает иначе. В Parquet маркеры одинаковы для всех файлов (фиксированные). В Avro sync marker уникален для каждого файла — это позволяет детектировать конкатенацию и повреждения.
Data Blocks
Основная структура хранения данных. Каждый блок содержит группу сериализованных объектов, записанных подряд:
Object Count (Avro long)
Avro long (variable-length zigzag encoding). Количество объектов (записей) в этом блоке. Зависит от sync interval при записи.Block Size in bytes (Avro long)
Avro long — размер сериализованных данных в байтах ПОСЛЕ компрессии (если avro.codec != null). Позволяет пропустить весь блок без десериализации.Sync Marker (16 байт — копия из header)
Те же 16 байт, что записаны в header файла. Маркер конца блока. Позволяет ридеру верифицировать целостность и синхронизировать позицию.Block size указывает размер после компрессии. Это позволяет эффективно пропускать блоки: ридер читает count и size, затем выполняет seek на size байт + 16 (sync marker), не декодируя содержимое.
Параллельное чтение
MapReduce и Spark делят файл на splits по диапазонам байт. Каждый reader получает свой диапазон и сканирует его на наличие sync marker. Найдя маркер, reader начинает чтение со следующего полного блока. Это обеспечивает параллельную обработку без центрального координатора.
Алгоритм:
- Reader получает диапазон
[start, end)файла - Если
start > 0— seek вперёд, сканируя по 16 байт за раз, ища sync marker - Нашёл sync marker → следующие байты — начало нового блока (count)
- Читает блоки, пока не выйдет за
end
В отличие от Parquet, где параллелизм определяется row group offsets из footer (точные позиции), Avro требует линейного сканирования для поиска sync marker. Это делает начало чтения чуть медленнее, но зато не требует предварительного чтения footer.
Кодеки компрессии
Avro поддерживает шесть кодеков компрессии. Кодек указывается в avro.codec metadata и применяется ко всему содержимому data block целиком — объекты сжимаются вместе, не по отдельности:
| Кодек | Стандарт | Характеристики |
|---|---|---|
null | — | Без компрессии. Минимальный CPU overhead. По умолчанию. |
deflate | RFC 1951 | zlib без header (raw deflate). Хорошее сжатие, умеренная скорость. |
snappy | Google Snappy | Быстрая компрессия и декомпрессия. В Avro: data + 4-byte CRC32. |
bzip2 | — | Высокое сжатие, медленная скорость. Блочный алгоритм. |
xz | LZMA2 | Максимальное сжатие, самая медленная компрессия. Для архивов. |
zstandard | Zstd (Facebook) | Настраиваемый баланс скорости и сжатия. Рекомендуется для новых проектов. |
Snappy — скорость
Snappy: приоритет скорости. ~250 MB/s компрессия, ~500 MB/s декомпрессия. Степень сжатия ~1.5-2x. Добавляет CRC32 checksum к каждому сжатому блоку. Быстрая компрессия и декомпрессия
CRC32 checksum per block
Скромное сжатие (~1.5–2x)
Zstandard — баланс
Zstandard: настраиваемый уровень 1–22. На уровне 3 — скорость близка к Snappy, сжатие ближе к deflate. Поддерживает dictionary compression. Настраиваемый уровень (1–22)
Хорошее сжатие при высокой скорости
Dictionary compression
XZ — максимальное сжатие
XZ (LZMA2): максимальное сжатие среди всех кодеков. Скорость компрессии ~10–30 MB/s. Декомпрессия быстрее. Для write-once архивного хранения. Лучшее соотношение сжатия
Очень медленная компрессия
Высокое потребление CPU и памяти
Snappy в Avro отличается от «чистого» Snappy: после каждого сжатого блока добавляется 4-байтовый CRC32 checksum. Ридер Avro обязан проверять CRC32 при декомпрессии — это защита целостности на уровне отдельного блока. Другие форматы (Parquet, ORC) используют Snappy без CRC32, полагаясь на checksums уровня файловой системы (HDFS block checksums).
Header-first vs footer-first
Расположение метаданных определяет паттерн использования формата:
Header-first (Avro):
- Запись: header пишется один раз, блоки дописываются последовательно
- Чтение: начинаем с начала, не нужен random access
- Подходит: потоковая обработка, append-heavy workloads, pipe между процессами
- Не подходит: аналитические запросы (нет column-level access, нет predicate pushdown)
Footer-first (Parquet, ORC):
- Запись: данные пишутся последовательно, footer записывается в конце
- Чтение: seek к концу → footer → seek к нужным column chunks
- Подходит: аналитические запросы, column pruning, predicate pushdown
- Не подходит: потоковая обработка (нужен весь файл для чтения footer)
Сравнение контейнеров: Avro vs Parquet vs ORC
| Характеристика | Avro OCF | Parquet | ORC |
|---|---|---|---|
| Magic bytes | Obj + 0x01 (4 байта) | PAR1 (4 байта) | ORC (3 байта) |
| Расположение схемы | Header (начало файла) | Footer (конец файла) | Footer (конец файла) |
| Сериализация метаданных | Avro binary | Apache Thrift | Protocol Buffers |
| Маркеры целостности | 16-byte sync marker | (CRC per page) | |
| Единица данных | Data Block (row-oriented) | Row Group / Column Chunk | Stripe / Column Streams |
| Параллельное чтение | Scan для sync marker | По Row Group offsets из footer | По Stripe offsets из footer |
| Потоковая запись | Да — header пишется первым | Нет — footer в конце | Нет — footer в конце |
| Компрессия | Per data block (все колонки) | Per column page | Per column stream |
Ключевые выводы
- Object Container File — контейнер Avro с header-first архитектурой: magic bytes → metadata map → sync marker → data blocks
- Magic bytes
Obj+0x01(hex:4F 62 6A 01) идентифицируют формат и версию - Sync marker (16 random bytes) разделяет блоки и обеспечивает параллельное чтение через scan
- Data block = count + size + serialized objects + sync — самодостаточная единица, компрессия применяется ко всему блоку
- Схема путешествует с данными —
avro.schemaв header позволяет десериализовать файл без внешнего schema registry - Zstandard — рекомендуемый кодек для новых проектов (настраиваемый баланс скорости и сжатия)