IPC Format
Зачем Arrow IPC
Arrow определяет формат в памяти — но память одного процесса недоступна другому. Чтобы передать Arrow-данные между процессами, между нодами кластера, или сохранить на диск — нужна сериализация.
Arrow IPC (Inter-Process Communication) — это сериализация, спроектированная для zero-copy десериализации: получатель может работать с данными напрямую из полученного буфера без парсинга, копирования и перекодирования.
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), не парсинг потока байтов
Arrow использует FlatBuffers только для метаданных (Schema, RecordBatch header, Dictionary header). Сами данные (buffers) лежат рядом — без FlatBuffers-обёртки.
Encapsulated Message Format
Каждое IPC-сообщение состоит из трёх частей:
- Continuation marker — 4 байта:
0xFFFFFFFF - Metadata — FlatBuffers-сериализованный Message (header type + header + body length)
- Body — конкатенация data buffers с padding
Metadata Length (4B, LE)
4 байта: длина FlatBuffers metadata в байтах (little-endian Int32). Нужна для skip — можно пропустить metadata и перейти к body.Padding (до 8-байтовой границы)
Padding между metadata и body. Обеспечивает 8-байтовое выравнивание body. Заполнен нулями.Continuation marker 0xFFFFFFFF — не просто magic number. В legacy Arrow IPC (до v0.15) первые 4 байта были metadata length. 0xFFFFFFFF как length невалиден — это позволяет различать форматы. Все современные имплементации используют continuation marker.
Message Types
Arrow IPC определяет четыре типа сообщений:
| Тип | Header Type | Назначение |
|---|---|---|
| Schema | Schema | Описание всех полей: имена, типы, nullable, metadata |
| RecordBatch | RecordBatch | Данные: массив nodes (lengths + null_counts) + массив buffer descriptors (offset + length) |
| DictionaryBatch | DictionaryBatch | Dictionary для dictionary-encoded колонок. id + isDelta + RecordBatch с данными dictionary |
| None | — | End-of-stream marker (metadata length = 0) |
RecordBatch: nodes и buffers
RecordBatch header содержит два массива:
nodes — по одному FieldNode на каждый массив (включая дочерние). Порядок: depth-first traversal по Schema.
length— количество элементов в массивеnull_count— количество NULL-значений
buffers — по одному Buffer descriptor на каждый физический буфер.
offset— смещение от начала bodylength— длина буфера в байтах
Пример: 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):
Schema Message
Первое сообщение: Schema. Описывает все поля. После этого получатель знает типы и может аллоцировать структуры.Свойства потокового формата:
- Однопроходное чтение — получатель не возвращается назад
- Произвольное количество RecordBatch — каждый самодостаточен
- Нет random access — чтобы прочитать batch N, нужно пропустить batches 0..N-1
- Нет footer — размер потока неизвестен заранее
File Format (IPC File / Feather v2)
Arrow IPC File добавляет magic number и footer для random access:
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-байтовой границы. Обеспечивает выравнивание первого сообщения.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 по индексу — без чтения предшествующих батчей.
Feather v2 — это тот же Arrow IPC File format. Библиотека pyarrow.feather и pandas read_feather читают и пишут именно Arrow IPC Files. Feather v1 (устаревший) использовал собственный формат — не совместим с Arrow IPC.
DictionaryBatch: потоковые словари
Dictionary-encoded колонки требуют отправки словаря до RecordBatch, который на него ссылается.
Протокол:
- Schema содержит field с dictionary type и dictionary id
- Перед RecordBatch идёт DictionaryBatch с тем же id и данными словаря
- 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 может быть сжат независимо.
Формат сжатого буфера:
- Uncompressed length — 8 байт (Int64, LE). Если -1 — буфер не сжат
- Compressed data — сжатый блок
Поддерживаемые кодеки:
- LZ4_FRAME — быстрая компрессия/декомпрессия, умеренный ratio
- ZSTD — лучший ratio при умеренной скорости
Сжатие в Arrow IPC ломает zero-copy: получатель должен аллоцировать буфер и декомпрессировать данные. Используйте сжатие только для передачи по сети или записи на диск — не для IPC между процессами на одной машине, где shared memory доступна.
Zero-Copy: как это работает
Zero-copy десериализация — главное преимущество Arrow IPC. Получатель создаёт Arrow-массивы, указывающие прямо в полученный буфер:
- Получить буфер (mmap файла, recv из сокета, shared memory)
- Прочитать metadata через FlatBuffers — без аллокации
- Для каждого buffer descriptor — вычислить указатель:
base + offset - Создать 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: когда что
| Критерий | Stream | File |
|---|---|---|
| Random access | Нет | O(1) по индексу batch |
| Потоковая передача | Pipe, socket, HTTP | Нужен seek |
| Размер заранее | Неизвестен | Известен (footer) |
| Чтение с конца | Невозможно | Footer → blocks |
| Типичное применение | Flight, streaming ETL | Feather, кэш, обмен файлами |
| Magic number | ARROW1…ARROW1 |
Ключевые выводы
- FlatBuffers — metadata без парсинга: O(1) доступ к полям, zero allocation при чтении
- Encapsulated message — continuation marker (0xFFFFFFFF) + metadata length + FlatBuffers header + body
- Stream — Schema → DictionaryBatch* → RecordBatch* → EOS. Однопроходное чтение, без random access
- File (Feather v2) — ARROW1 + messages + Footer + ARROW1. Random access через Block descriptors в footer
- DictionaryBatch — словари передаются отдельно, delta dictionaries для потоковых обновлений
- IPC compression (LZ4/ZSTD) — уменьшает размер за счёт потери zero-copy при чтении
- Zero-copy — получатель строит Arrow arrays как указатели в исходный буфер, без memcpy