Arrow IPC и Flight: передача данных без сериализации
Проблема: передача данных между процессами
Arrow решает проблему in-memory формата внутри одного процесса. Но данные нужно передавать: между микросервисами, между узлами кластера, между клиентом и сервером.
Традиционный подход: сериализовать данные в JSON/CSV/Protobuf, передать по сети, десериализовать. На каждой границе — O(n) работы по конвертации.
Arrow предлагает два протокола, устраняющих эту проблему:
- Arrow IPC — бинарный формат для записи Arrow-данных в файл или поток
- Arrow Flight — gRPC-протокол для высокопроизводительной передачи Arrow-данных по сети
Arrow IPC: два формата
Arrow IPC (Inter-Process Communication) определяет два формата для сериализации RecordBatch:
Stream Format
Последовательность сообщений без возможности произвольного доступа. Оптимален для потоковой передачи.
Каждое сообщение содержит:
- Flatbuffers metadata — описание layout буферов в body
- Body — сами буферы данных, выровненные по 64 байт
File Format (Feather)
Добавляет footer с индексами, позволяя произвольный доступ к RecordBatch по номеру.
Footer позволяет читать конкретный RecordBatch без сканирования всего файла. Это аналог row group index в Parquet, но для in-memory формата.
Zero-Copy десериализация
Ключевое свойство IPC: body сообщения содержит Arrow-буферы в нативном формате. Десериализация — это только чтение metadata и установка указателей на буферы. Сами данные не копируются и не конвертируются.
use arrow::ipc::reader::StreamReader;
use std::io::Cursor;
// Чтение Arrow IPC stream
let reader = StreamReader::try_new(cursor, None)?;
for batch in reader {
let batch = batch?;
// batch.column(0) указывает напрямую на буферы из IPC --
// никакого memcpy, никакой десериализации значений
println!("Rows: {}", batch.num_rows());
}
Для сравнения: десериализация CSV требует парсинга каждого значения из текста в бинарный формат. Для 1 миллиона строк с 10 колонками это 10 миллионов конвертаций. Arrow IPC — ноль конвертаций.
Arrow Flight: данные по сети
Arrow Flight — протокол поверх gRPC для передачи Arrow-данных по сети. Flight не заменяет IPC, а использует его как формат сообщений внутри gRPC-вызовов.
Зачем Flight, если есть gRPC + Protobuf
gRPC с Protobuf требует сериализации данных в Protobuf wire format и обратно. Для аналитических нагрузок (миллионы строк) overhead сериализации доминирует. Flight передаёт Arrow IPC буферы напрямую через gRPC streams, минуя Protobuf-сериализацию данных.
Основные RPC-методы Flight
| Метод | Описание | Аналогия |
|---|---|---|
GetFlightInfo | Получить метаданные (schema, endpoints) | DESCRIBE TABLE |
DoGet | Получить поток RecordBatch | SELECT * FROM ... |
DoPut | Отправить поток RecordBatch на сервер | INSERT INTO ... |
DoExchange | Двунаправленный поток (запрос + ответ) | Интерактивный запрос |
ListFlights | Список доступных datasets | SHOW TABLES |
GetSchema | Получить схему без данных | DESCRIBE (лёгкий) |
DoAction | Произвольное действие (create, delete) | Административные команды |
Endpoints и параллельное чтение
GetFlightInfo возвращает список endpoints — адреса серверов, с которых можно получить части данных. Это позволяет параллельное чтение:
GetFlightInfo("table_x") → [
Endpoint { locations: ["server-1:8815"], ticket: "partition_0" },
Endpoint { locations: ["server-2:8815"], ticket: "partition_1" },
Endpoint { locations: ["server-3:8815"], ticket: "partition_2" },
]
Клиент отправляет DoGet на каждый endpoint параллельно и объединяет потоки RecordBatch. Ballista (распределённый DataFusion) использует именно этот механизм для передачи данных между executor-ами.
Flight SQL
Flight SQL — расширение Flight для SQL-интерфейса. Добавляет семантику SQL-запросов поверх Flight transport:
CommandStatementQuery— выполнить SQL-запрос и получить результат как поток RecordBatchCommandPreparedStatementQuery— prepared statements с биндингамиCommandGetCatalogs/CommandGetSchemas/CommandGetTables— metadata API
Flight SQL — это альтернатива JDBC/ODBC с нативной Arrow-передачей данных. Вместо конвертации результатов в JDBC ResultSet (Java объекты) или ODBC буферы (C-структуры), клиент получает Arrow RecordBatch напрямую.
DataFusion поддерживает Arrow IPC stream format нативно: результаты запросов можно стримить как Arrow IPC без промежуточной сериализации. Flight SQL сервер на базе DataFusion — это тонкий слой gRPC поверх уже Arrow-нативного движка.
Производительность: Arrow IPC vs альтернативы
| Формат | Сериализация | Десериализация | Размер | Совместимость |
|---|---|---|---|---|
| Arrow IPC | Быстрая (копирование буферов) | Zero-copy | 1x (не сжат) | Arrow-native |
| Parquet | Медленная (encode + compress) | Медленная (decompress + decode) | 0.1-0.3x | Универсальная |
| CSV | Быстрая (текст) | Медленная (parse per value) | 2-5x | Универсальная |
| JSON | Быстрая (текст) | Медленная (parse per value) | 3-10x | Универсальная |
| Protobuf | Средняя (encode) | Средняя (decode) | 0.3-0.5x | gRPC-native |
Arrow IPC выигрывает по скорости десериализации: она фактически бесплатна. Проигрывает по размеру (нет компрессии по умолчанию). Для передачи по сети Flight поддерживает опциональное LZ4/ZSTD-сжатие буферов.
Где используется Flight
- Ballista — распределённый DataFusion, exchange данных между executor-ами через Flight
- Dremio — Arrow Flight как основной интерфейс доступа к данным
- InfluxDB 3.0 — Flight SQL для запросов к time-series данным (движок на DataFusion)
- ADBC (Arrow Database Connectivity) — использует Flight SQL как transport layer
Итоги
- Arrow IPC — бинарный формат для Arrow-данных: stream (потоковый) и file (с индексом)
- Zero-copy десериализация: чтение metadata + установка указателей, данные не копируются
- Arrow Flight — gRPC-протокол для передачи Arrow по сети без Protobuf-сериализации
- Flight SQL добавляет SQL-семантику (запросы, prepared statements, metadata)
- Ballista и InfluxDB используют Flight для распределённой передачи данных