Flight Protocol
Проблема: почему JDBC/ODBC не справляются
JDBC и ODBC проектировались в 1990-х для OLTP-нагрузок — одна строка за раз, request-response модель. Для аналитических запросов, возвращающих миллионы строк, это создаёт три бутылки:
- Сериализация: сервер конвертирует колоночные данные в строковый формат (row-by-row)
- Десериализация: клиент парсит строки обратно в колоночные структуры
- Однопоточность: один TCP-стрим, один request за раз
Результат: движок выполнил запрос за 100ms, а передача 10M строк через JDBC занимает 30 секунд.
Движок (колонки)
Движок хранит и обрабатывает данные в колоночном формате (Arrow RecordBatch). Результат запроса — набор колонок.Row-by-row serialize
Сериализация: движок конвертирует колоночные данные в строчный формат JDBC ResultSet — одна строка за один вызов next(). Это O(rows × columns) копирований.TCP (1 stream)
TCP-соединение: один стрим, один request/response за раз. Нет параллельных потоков данных. Нет multiplexing.Row-by-row deserialize
Десериализация: клиент конвертирует строчные данные обратно в колоночный формат (pandas DataFrame, Arrow Table). Ещё одно O(rows × columns) копирование.Клиент (колонки)
Клиент получает данные в нужном формате, но потратил 10-100× больше времени на передачу, чем на сам запрос.Columnar → Row → Wire → Row → Columnar = три конвертации, одна TCP-сессия
Arrow Flight: колоночный RPC
Apache Arrow Flight — RPC-фреймворк, построенный поверх gRPC (HTTP/2 + Protocol Buffers). Вместо строчной сериализации Flight передаёт данные в формате Arrow IPC — тем же форматом, что лежит в памяти движка.
Движок (Arrow)
Движок хранит результат в Arrow RecordBatch. Никакой конвертации — батч передаётся в том же формате, в каком он существует в памяти.IPC serialize
Arrow IPC сериализация: RecordBatch → FlatBuffers metadata + raw буферы. Это O(1) — метаданные + указатели на буферы, не копирование данных (если ОС поддерживает sendfile/splice).IPC deserialize
IPC десериализация: FlatBuffers metadata + raw буферы → Arrow RecordBatch. Zero-copy возможен: клиент строит RecordBatch как указатели в полученный буфер.Клиент (Arrow)
Клиент получает данные уже в Arrow-формате. Нет конвертации — данные готовы для DuckDB, Polars, pandas (через .to_pandas()).Arrow → IPC → HTTP/2 → IPC → Arrow = zero format conversion, parallel streams
Преимущества:
- Нет format conversion: данные остаются в Arrow IPC от сервера до клиента
- HTTP/2 multiplexing: несколько потоков данных по одному TCP-соединению
- gRPC streaming: server-side и bidirectional стримы — данные текут, пока генерируются
- Параллелизм: Flight возвращает несколько endpoints — клиент забирает данные с разных нод параллельно
6 методов Flight
Flight определяет 6 RPC-методов через gRPC service definition:
Типичный flow: клиент вызывает GetFlightInfo → получает список endpoints с Ticket → вызывает DoGet на каждом endpoint параллельно → собирает RecordBatch.
Параллельный fetch: FlightEndpoint
Ключевая идея Flight — distributed data retrieval. GetFlightInfo возвращает несколько FlightEndpoint, каждый указывает на конкретный сервер:
Клиент → GetFlightInfo(query)
Клиент отправляет запрос координатору. GetFlightInfo возвращает FlightInfo с 3 endpoints — по одному на каждый узел кластера, хранящий часть данных.Результат: concat(batches)
Клиент получает RecordBatch от каждого endpoint и конкатенирует. Порядок партиций не гарантирован — это ответственность приложения (или движка).Это позволяет линейное масштабирование: 3 ноды → 3× пропускная способность. Для ClickHouse, Dremio, InfluxDB 3 — это штатный режим работы.
Flight SQL: SQL поверх Flight
Flight — это транспорт, не привязанный к SQL. Flight SQL — надстройка, добавляющая SQL-семантику через Protobuf-команды:
| Операция | Flight SQL команда | Flight метод |
|---|---|---|
| Выполнить SELECT | CommandStatementQuery | GetFlightInfo → DoGet |
| Выполнить INSERT/UPDATE | CommandStatementUpdate | DoPut |
| Получить список таблиц | CommandGetTables | GetFlightInfo → DoGet |
| Получить схему таблицы | CommandGetTableTypes | GetFlightInfo → DoGet |
| Prepared statement | ActionCreatePreparedStatementRequest | DoAction |
Flight SQL — это wire protocol (как MySQL protocol или PostgreSQL wire protocol). ADBC — это client API (как JDBC). Одно определяет что передаётся по сети, другое — какие функции вызывает приложение.
Databases, поддерживающие Flight SQL: Dremio, Apache Doris, InfluxDB 3, Apache Arrow DataFusion (через Ballista), StarRocks.
ADBC: Arrow Database Connectivity
ADBC — клиентский API для работы с базами данных через Arrow. Ключевое отличие от JDBC/ODBC:
- JDBC/ODBC: row-oriented API → данные конвертируются в строки при передаче
- ADBC: Arrow-native API → данные остаются в колоночном формате на всём пути
JDBC
JDBC: данные проходят через row-by-row ResultSet. Каждый next() возвращает одну строку. Для аналитики — O(rows) вызовов + конвертация в колонки на клиенте.ADBC
ADBC: данные передаются как Arrow RecordBatch. fetch_arrow_table() возвращает таблицу целиком в Arrow-формате. Нет row-by-row конвертации.ADBC может работать с любым бэкендом, не только Flight SQL:
- Flight SQL driver — для баз с Flight SQL endpoint
- PostgreSQL driver — использует
COPY ... (FORMAT binary)+ Arrow конвертацию - SQLite driver — через нативный API + Arrow буферы
- Snowflake driver — через Snowflake Arrow result set API
import adbc_driver_flightsql.dbapi as flight_sql
# Подключение к Flight SQL серверу
conn = flight_sql.connect("grpc+tls://my-database:8815",
db_kwargs={"username": "user",
"password": "pass"})
with conn.cursor() as cur:
cur.execute("SELECT user_id, name FROM users WHERE score > 80")
# Arrow-нативный результат — без row-by-row конвертации
table = cur.fetch_arrow_table()
print(table.schema)
# Или как pandas DataFrame
df = cur.fetch_arrow_table().to_pandas()
ADBC реализует Python DB-API 2.0 — совместим с SQLAlchemy, Ibis и другими ORM/query builders через стандартный интерфейс connect() / cursor() / execute().
Производительность: Flight vs JDBC
Конкретные числа зависят от hardware и данных, но порядок ускорения:
| Сценарий | JDBC | Flight + ADBC | Ускорение |
|---|---|---|---|
| 10M строк × 10 колонок | ~30 сек | ~1-2 сек | 15-30× |
| 100M строк, wide table | ~5 мин | ~10-15 сек | 20-30× |
| Distributed (3 ноды) | Sequential | Parallel endpoints | 3× дополнительно |
Основной выигрыш — не скорость сети, а отсутствие сериализации. На localhost разница ещё заметнее: JDBC тратит CPU на row↔column конвертацию, Flight передаёт буферы напрямую.
Ключевые выводы
- JDBC/ODBC — row-oriented протоколы из 1990-х. Тройная конвертация (column→row→wire→row→column) — бутылка для аналитики
- Arrow Flight — gRPC-based RPC с данными в Arrow IPC формате. Нет format conversion, HTTP/2 multiplexing, параллельный fetch через FlightEndpoint
- 6 методов — GetFlightInfo (метаданные), DoGet (download), DoPut (upload), ListFlights (discovery), DoAction (extensible), DoExchange (bidirectional)
- FlightEndpoint — distributed data retrieval: один GetFlightInfo → N параллельных DoGet с разных нод
- Flight SQL — SQL-семантика (CommandStatementQuery, CommandGetTables) поверх Flight. Wire protocol, не client API
- ADBC — Arrow-native client API. Работает с Flight SQL, PostgreSQL, SQLite, Snowflake. DB-API 2.0 совместим
- Производительность — 15-30× vs JDBC на аналитических запросах. Основной выигрыш: нет row↔column конвертации