Learning Platform
Глоссарий Troubleshooting
Урок 08.03 · 35 мин
Продвинутый
Apache ArrowIPCFlatBuffersStream FormatFile FormatZero-CopySerializationArrow Flight

IPC Format

Зачем Arrow IPC

Arrow определяет формат в памяти — но память одного процесса недоступна другому. Чтобы передать Arrow-данные между процессами, между нодами кластера, или сохранить на диск — нужна сериализация.

Arrow IPC (Inter-Process Communication) — это сериализация, спроектированная для zero-copy десериализации: получатель может работать с данными напрямую из полученного буфера без парсинга, копирования и перекодирования.

NOTE

Arrow IPC — это не Parquet. Parquet сжимает и кодирует данные (dictionary, RLE, delta, bit packing) для минимизации размера на диске. Arrow IPC сохраняет данные максимально близко к in-memory layout — жертвуя размером ради скорости десериализации.

FlatBuffers: метаданные без парсинга

Arrow IPC использует FlatBuffers (Google) для сериализации метаданных (схема, типы, lengths). FlatBuffers — бинарный формат с O(1) доступом к полям без распаковки.

В отличие от Protobuf:

  • FlatBuffers не требует десериализации — данные читаются напрямую из буфера через vtable offsets
  • Нет аллокации памяти при чтении
  • Доступ к полю — два чтения из памяти (vtable → value), не парсинг потока байтов
FlatBuffers vs Protobuf: доступ к метаданным
ProtobufВесь буфер парсится в объект (allocate + copy). Каждое поле декодируется из varint/length-delimited. Любое чтение = полный проход.
FlatBuffersДанные читаются напрямую из исходного буфера. vtable содержит offsets к каждому полю. Нет копирования — O(1) доступ.

Arrow использует FlatBuffers только для метаданных (Schema, RecordBatch header, Dictionary header). Сами данные (buffers) лежат рядом — без FlatBuffers-обёртки.

Encapsulated Message Format

Каждое IPC-сообщение состоит из трёх частей:

  1. Continuation marker — 4 байта: 0xFFFFFFFF
  2. Metadata — FlatBuffers-сериализованный Message (header type + header + body length)
  3. Body — конкатенация data buffers с padding
Encapsulated Message: структура одного сообщения
0xFFFFFFFF (continuation, 4B)4 байта: 0xFF 0xFF 0xFF 0xFF. Continuation marker отличает новый формат (Arrow 0.15+) от legacy. Всегда один и тот же.

Metadata Length (4B, LE)

4 байта: длина FlatBuffers metadata в байтах (little-endian Int32). Нужна для skip — можно пропустить metadata и перейти к body.
Metadata (FlatBuffers)FlatBuffers-сериализованный Message. Содержит: version (V5), header_type (Schema/RecordBatch/DictionaryBatch), header (type-specific), bodyLength. Padded до 8 байт.

Padding (до 8-байтовой границы)

Padding между metadata и body. Обеспечивает 8-байтовое выравнивание body. Заполнен нулями.
Message BodyКонкатенация data buffers массива. Каждый buffer padded до 8 байт. Порядок буферов определён в RecordBatch header (nodes + buffers arrays).
WARNING

Continuation marker 0xFFFFFFFF — не просто magic number. В legacy Arrow IPC (до v0.15) первые 4 байта были metadata length. 0xFFFFFFFF как length невалиден — это позволяет различать форматы. Все современные имплементации используют continuation marker.

Message Types

Arrow IPC определяет четыре типа сообщений:

ТипHeader TypeНазначение
SchemaSchemaОписание всех полей: имена, типы, nullable, metadata
RecordBatchRecordBatchДанные: массив nodes (lengths + null_counts) + массив buffer descriptors (offset + length)
DictionaryBatchDictionaryBatchDictionary для dictionary-encoded колонок. id + isDelta + RecordBatch с данными dictionary
NoneEnd-of-stream marker (metadata length = 0)
Три типа сообщений Arrow IPC
SchemaПервое сообщение в потоке. Описывает все поля таблицы. Нет body — только metadata. Содержит endianness для cross-platform совместимости.
RecordBatchОсновное сообщение с данными. nodes[] описывает каждый массив (length, null_count). buffers[] — offsets и lengths буферов в body.
DictionaryBatchСловарь для dictionary-encoded колонок. id — идентификатор словаря (связывает с полем в Schema). isDelta — инкрементальное обновление.

RecordBatch: nodes и buffers

RecordBatch header содержит два массива:

nodes — по одному FieldNode на каждый массив (включая дочерние). Порядок: depth-first traversal по Schema.

  • length — количество элементов в массиве
  • null_count — количество NULL-значений

buffers — по одному Buffer descriptor на каждый физический буфер.

  • offset — смещение от начала body
  • length — длина буфера в байтах

Пример: Struct<name: Utf8, age: Int32> с 3 элементами:

nodes:
 [0] Struct: length=3, null_count=1
 [1] Utf8: length=3, null_count=0
 [2] Int32: length=3, null_count=0

buffers:
 [0] Struct validity: offset=0, length=1
 [1] Utf8 validity: offset=64, length=1
 [2] Utf8 offsets: offset=128, length=16
 [3] Utf8 data: offset=192, length=8
 [4] Int32 validity: offset=256, length=1
 [5] Int32 data: offset=320, length=12

Порядок bufers — depth-first, для каждого массива: validity → offsets → data.

Stream Format

Arrow IPC Stream — последовательность сообщений, пригодная для потоковой передачи (pipe, socket, HTTP chunked):

Arrow IPC Stream Format

Schema Message

Первое сообщение: Schema. Описывает все поля. После этого получатель знает типы и может аллоцировать структуры.
DictionaryBatch(es)Опционально: словари для dictionary-encoded колонок. Должны идти ПЕРЕД первым RecordBatch. id связывает словарь с полем в Schema.
RecordBatch(es)Основные данные. Может быть один или несколько RecordBatch. Каждый — независимый набор строк с одной и той же Schema. Потребитель может обрабатывать по мере получения.
EOS (0xFFFFFFFF + 0x00000000)End-of-stream: continuation marker + metadata length = 0. 8 байт: 0xFFFFFFFF 0x00000000. Получатель знает, что данных больше не будет.

Свойства потокового формата:

  • Однопроходное чтение — получатель не возвращается назад
  • Произвольное количество RecordBatch — каждый самодостаточен
  • Нет random access — чтобы прочитать batch N, нужно пропустить batches 0..N-1
  • Нет footer — размер потока неизвестен заранее

File Format (IPC File / Feather v2)

Arrow IPC File добавляет magic number и footer для random access:

Arrow IPC File Format

ARROW1 (6 байт — magic number)

6 байт: 'ARROW1' (hex: 41 52 52 4F 57 31). Magic number файлового формата Arrow IPC. Не путать с PAR1 (Parquet).

Padding (2B до 8-байт. границы)

2 байта padding до 8-байтовой границы. Обеспечивает выравнивание первого сообщения.
СообщенияSchema + DictionaryBatch(es) + RecordBatch(es). Тот же формат, что и в Stream, но без EOS маркера в конце.
Footer (FlatBuffers)Содержит Schema (повтор) + массив Block descriptors. Каждый Block: offset, metadataLength, bodyLength — для random access к любому RecordBatch.

Footer Length (4B, LE)

4 байта: длина Footer в байтах (little-endian Int32). Ридер читает с конца: ARROW1 → footer length → footer → blocks.

ARROW1 (6 байт — magic number)

6 байт: 'ARROW1'. Как и Parquet (PAR1...PAR1), файл обрамлён magic number с обоих концов.

Footer содержит массив Block структур для каждого RecordBatch:

Block {
 offset: Int64, // смещение от начала файла
 metadataLength: Int32, // длина metadata (без body)
 bodyLength: Int64 // длина body
}

Это даёт O(1) random access к любому RecordBatch по индексу — без чтения предшествующих батчей.

TIP

Feather v2 — это тот же Arrow IPC File format. Библиотека pyarrow.feather и pandas read_feather читают и пишут именно Arrow IPC Files. Feather v1 (устаревший) использовал собственный формат — не совместим с Arrow IPC.

DictionaryBatch: потоковые словари

Dictionary-encoded колонки требуют отправки словаря до RecordBatch, который на него ссылается.

Протокол:

  1. Schema содержит field с dictionary type и dictionary id
  2. Перед RecordBatch идёт DictionaryBatch с тем же id и данными словаря
  3. RecordBatch содержит только indices array — ссылки на словарь

Delta dictionaries: если isDelta = true, новые значения добавляются к существующему словарю (indices со старыми значениями остаются валидными). Полезно для потоков, где новые категории появляются по мере поступления данных.

// Поток с delta dictionary:
Schema(status: Dictionary<Int8, Utf8>, id=0)
DictionaryBatch(id=0, data=["active", "pending"]) // initial
RecordBatch(indices=[0, 1, 0, 0]) // 4 rows
DictionaryBatch(id=0, isDelta=true, data=["closed"]) // adds index 2
RecordBatch(indices=[0, 2, 1, 0]) // uses new value

IPC Compression

С Arrow 1.0 IPC поддерживает сжатие буферов внутри RecordBatch. Каждый buffer в body может быть сжат независимо.

Формат сжатого буфера:

  1. Uncompressed length — 8 байт (Int64, LE). Если -1 — буфер не сжат
  2. Compressed data — сжатый блок

Поддерживаемые кодеки:

  • LZ4_FRAME — быстрая компрессия/декомпрессия, умеренный ratio
  • ZSTD — лучший ratio при умеренной скорости
IPC Buffer: сжатый vs несжатый
Без сжатияБуфер в body — raw данные, padded до 8 байт. Zero-copy: получатель использует буфер напрямую.
Со сжатием (LZ4/ZSTD)Буфер начинается с 8-байтового uncompressed length, затем сжатые данные. Требует аллокацию + decompress при чтении.
WARNING

Сжатие в Arrow IPC ломает zero-copy: получатель должен аллоцировать буфер и декомпрессировать данные. Используйте сжатие только для передачи по сети или записи на диск — не для IPC между процессами на одной машине, где shared memory доступна.

Zero-Copy: как это работает

Zero-copy десериализация — главное преимущество Arrow IPC. Получатель создаёт Arrow-массивы, указывающие прямо в полученный буфер:

  1. Получить буфер (mmap файла, recv из сокета, shared memory)
  2. Прочитать metadata через FlatBuffers — без аллокации
  3. Для каждого buffer descriptor — вычислить указатель: base + offset
  4. Создать Arrow Array, ссылающийся на эти указатели

Ни одного memcpy. Ни одного malloc (кроме тонких обёрток Array). Данные остаются в исходном буфере.

# Python: zero-copy через mmap
import pyarrow as pa
import mmap

with open("data.arrow", "rb") as f:
 mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
 reader = pa.ipc.open_file(mm)
 batch = reader.get_batch(0) # указатели в mmap, не копия

Stream vs File: когда что

КритерийStreamFile
Random access Нет O(1) по индексу batch
Потоковая передача Pipe, socket, HTTP Нужен seek
Размер заранееНеизвестенИзвестен (footer)
Чтение с концаНевозможно Footer → blocks
Типичное применениеFlight, streaming ETLFeather, кэш, обмен файлами
Magic numberARROW1…ARROW1

Ключевые выводы

  1. FlatBuffers — metadata без парсинга: O(1) доступ к полям, zero allocation при чтении
  2. Encapsulated message — continuation marker (0xFFFFFFFF) + metadata length + FlatBuffers header + body
  3. Stream — Schema → DictionaryBatch* → RecordBatch* → EOS. Однопроходное чтение, без random access
  4. File (Feather v2) — ARROW1 + messages + Footer + ARROW1. Random access через Block descriptors в footer
  5. DictionaryBatch — словари передаются отдельно, delta dictionaries для потоковых обновлений
  6. IPC compression (LZ4/ZSTD) — уменьшает размер за счёт потери zero-copy при чтении
  7. Zero-copy — получатель строит Arrow arrays как указатели в исходный буфер, без memcpy
Spark: zero-copy IPC между JVM и Python DataFusion: IPC и Flight в Rust ecosystem

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Arrow IPC использует FlatBuffers для сериализации метаданных. Какое ключевое преимущество FlatBuffers перед Protobuf в контексте Arrow IPC?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7