Колоночный формат: почему аналитика требует другой памяти
Проблема строчного хранения
Традиционные OLTP-базы (PostgreSQL, MySQL) хранят данные по строкам. Каждая строка таблицы лежит в памяти непрерывным блоком: все поля одной записи рядом.
Для транзакционных операций это идеально: INSERT, UPDATE, DELETE работают с целой строкой, и все её поля доступны за одно обращение к памяти.
Но аналитический запрос устроен иначе. SELECT AVG(salary) FROM employees читает одну колонку из десятка. При строчном хранении процессор загружает все поля каждой строки, чтобы извлечь единственное нужное значение. На таблице с 10 колонками 90% прочитанных байт выбрасываются.
Четыре преимущества колоночного формата
1. Column Pruning
При SELECT salary, age FROM employees колоночный движок читает ровно 2 буфера из 10. Строчный движок читает все 10 полей каждой строки и отбрасывает 8.
2. SIMD-векторизация
Значения одной колонки лежат в памяти последовательно (contiguous). Процессор обрабатывает 4-8 значений за одну SIMD-инструкцию (AVX2 / AVX-512 / NEON). При строчном хранении значения одного поля разбросаны через всю ширину строки — векторизация невозможна.
3. Компрессия
Однотипные значения в одной колонке сжимаются эффективнее. Run-Length Encoding для колонки department с повторяющимися “IT”, “HR”, “Sales” даёт 10-50x сжатие. Dictionary Encoding для low-cardinality строк заменяет строки индексами.
4. Cache Locality
CPU cache line — 64 байт. Для int64 колонки это 8 значений за одну загрузку. При строчном хранении каждое следующее значение salary отделено полной шириной строки — cache miss на каждом обращении.
Apache Arrow: универсальный in-memory стандарт
Apache Arrow — спецификация колоночного формата данных в оперативной памяти. Не библиотека, не движок, а именно спецификация byte layout.
Arrow vs Parquet: Parquet — формат хранения на диске (row groups, page compression). Arrow — формат обработки в RAM (буферы фиксированного размера, мгновенный произвольный доступ). Parquet оптимизирован для I/O, Arrow — для CPU.
Arrow определяет точный побайтовый layout для каждого типа данных. Это означает:
- Данные в Arrow в Python, Rust, Java, C++, Go занимают одни и те же байты
- Передача между языками — передача указателя, не сериализация
- Нет overhead конвертации при смене runtime
Zero-Copy: ключевое свойство
Zero-copy означает, что данные не копируются при передаче между компонентами. Python-процесс может передать Arrow-буфер Rust-расширению без единого memcpy. DataFusion получает данные из внешнего источника в Arrow формате и сразу обрабатывает — без десериализации.
Arrow до и после: мир без стандарта
До Arrow каждая система определяла собственный in-memory формат. Pandas использовал NumPy arrays, Spark — Tungsten binary format, R — SEXP vectors. Передача данных между системами означала сериализацию и десериализацию на каждой границе.
Pandas → (serialize) → bytes → (deserialize) → Spark → (serialize) → bytes → (deserialize) → R
С Arrow:
Pandas (Arrow) → shared memory → Spark (Arrow) → shared memory → DataFusion (Arrow)
Это не теоретическое преимущество. DataFusion построен на Arrow с самого начала: каждый оператор принимает и возвращает RecordBatch — основную единицу данных Arrow. Промежуточных форматов нет.
RecordBatch: основная единица данных
RecordBatch — группа колонок одинаковой длины с общей схемой. Это аналог “страницы” данных в Arrow мире.
// Структура RecordBatch (концептуально)
RecordBatch {
schema: Schema, // имена и типы колонок
columns: Vec<ArrayRef>, // сами данные (по колонке)
row_count: usize, // количество строк
}
DataFusion обрабатывает данные потоково: каждый оператор (filter, projection, join) получает поток RecordBatch, трансформирует и передаёт дальше. Типичный размер batch — 8192 строки, достаточно для эффективной векторизации и при этом помещается в L2 cache.
В следующем уроке мы разберём внутреннюю структуру Arrow-буферов: validity bitmap, offset buffer и data buffer — три компонента, из которых состоит каждая колонка.
Итоги
- Строчное хранение оптимально для OLTP, колоночное — для OLAP
- Колоночный формат даёт column pruning, SIMD, компрессию, cache locality
- Apache Arrow — единая спецификация колоночного in-memory формата
- Zero-copy: данные передаются указателем, не копированием
- RecordBatch — основная единица данных, поток RecordBatch — модель обработки DataFusion “text-emerald-400” size=“1.2em” /> RecordBatch — основная единица данных, поток RecordBatch — модель обработки DataFusion �м
- RecordBatch — основная единица данных, поток RecordBatch — модель обработки DataFusion ь обработки DataFusion