Learning Platform
Глоссарий Troubleshooting
Урок 02.01 · 12 мин
Начальный
Columnar FormatRow FormatOLAPApache ArrowZero-Copy

Колоночный формат: почему аналитика требует другой памяти

Проблема строчного хранения

Традиционные OLTP-базы (PostgreSQL, MySQL) хранят данные по строкам. Каждая строка таблицы лежит в памяти непрерывным блоком: все поля одной записи рядом.

Для транзакционных операций это идеально: INSERT, UPDATE, DELETE работают с целой строкой, и все её поля доступны за одно обращение к памяти.

Но аналитический запрос устроен иначе. SELECT AVG(salary) FROM employees читает одну колонку из десятка. При строчном хранении процессор загружает все поля каждой строки, чтобы извлечь единственное нужное значение. На таблице с 10 колонками 90% прочитанных байт выбрасываются.

Row Storage vs Column Storage
Row Storage (OLTP)Строчное хранение: все поля записи рядом в памяти, оптимально для INSERT/UPDATE/DELETE
Column Storage (OLAP)Колоночное хранение: значения одной колонки непрерывно, оптимально для аналитики

Четыре преимущества колоночного формата

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.

NOTE

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 формате и сразу обрабатывает — без десериализации.

Zero-Copy в действии
Python (PyArrow)Python-реализация Arrow через C++ backend. Интеграция с Pandas, Polars, DuckDB
Rust (arrow-rs)Чистая Rust-реализация Arrow. Основная зависимость DataFusion
Java (Arrow Java)Java-реализация Arrow с off-heap памятью для Spark и Flink
Arrow Memory (один byte layout)Все реализации работают с одним byte layout. Shared memory pointer вместо сериализации
Обработка без десериализацииДанные уже в нужном формате — нет overhead конвертации между языками

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.

TIP

В следующем уроке мы разберём внутреннюю структуру 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

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Почему строчное хранение неэффективно для аналитического запроса SELECT AVG(salary) FROM employees, если таблица содержит 10 колонок?

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

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

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

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