Learning Platform
Глоссарий Troubleshooting
Урок 02.02 · 15 мин
Средний
Arrow Memory LayoutValidity BitmapOffset BufferData BufferRecordBatchNull Handling

Arrow Memory Layout: буферы, bitmap и RecordBatch

Три буфера Arrow-массива

Каждая колонка (массив) в Arrow состоит из комбинации трёх типов буферов:

  • Validity Bitmap — битовая маска: 1 = значение присутствует, 0 = NULL
  • Data Buffer — сами значения, уложенные непрерывно
  • Offset Buffer — используется только для типов переменной длины (строки, списки)

Не каждый тип использует все три буфера. Числовые массивы обходятся двумя (validity + data), строковые — тремя (validity + offsets + data).

Fixed-Width типы: числа и boolean

Для типов фиксированного размера (Int32, Float64, Boolean) layout прямолинеен:

Int32 Array: [28, 34, NULL, 41]
Validity Bitmap (1 байт, 4 бита использовано)Битовая маска: бит=1 означает значение присутствует, бит=0 означает NULL. Проверка — побитовая операция O(1)
Data Buffer (16 байт = 4 x int32)4 значения по 4 байта = 16 байт. Позиция NULL содержит undefined данные (обычно 0)

Ключевой момент: позиция NULL в data buffer не пуста. Там может быть 0 или мусор. Определяет null именно validity bitmap, не значение в буфере. Это позволяет выделить буфер один раз и не сдвигать данные при появлении NULL.

Alignment и padding

Arrow требует, чтобы каждый буфер был выровнен по 64 байт (размер cache line). Если данные не кратны 64, в конце добавляется padding. Для массива из 5 значений Int32 (20 байт) буфер занимает 64 байт — 44 байта padding.

Variable-Width типы: строки и бинарные данные

Строковые типы (Utf8, LargeUtf8, Binary) добавляют третий буфер — offset buffer, который хранит начало каждого значения в data buffer.

Utf8 Array: ["Rust", "Go", NULL, "Python"]
Validity BitmapБитовая маска для строковых данных: бит=0 означает NULL, строка пропущена в data buffer
Offset Buffer (5 x int32 = 20 байт)N+1 смещений для N значений. Длина i-го значения = offset[i+1] - offset[i]
Data Buffer (12 байт)Все строки конкатенированы без разделителей. Offset buffer определяет границы

Формула длины i-го значения: offsets[i+1] - offsets[i]. Для NULL-значений длина 0 (offset не сдвигается). Offset buffer всегда содержит N+1 элементов, где N — количество значений.

Utf8 vs LargeUtf8

Utf8 использует int32 offsets — максимальный суммарный размер данных 2 GB. LargeUtf8 использует int64 offsets — до 2^63 байт. DataFusion по умолчанию работает с Utf8, переключение на LargeUtf8 требуется только для колонок с огромным суммарным объёмом текста.

Null Handling: дизайн-решение Arrow

В Arrow NULL — не значение, а свойство позиции. Это принципиальное отличие от SQL (где NULL — специальное значение) и от Pandas (где NaN используется для NULL в числовых колонках).

Преимущества подхода Arrow:

  • Любой тип может быть nullable без изменения data layout
  • Null-check — побитовая операция, не сравнение с sentinel
  • Агрегации (SUM, AVG) пропускают NULL через bitmap mask, не проверяя каждое значение
// Проверка null в arrow-rs
let array: Int32Array = /* ... */;

// Побитовая проверка -- O(1) per element
if array.is_null(2) {
    // позиция 2 -- NULL
}

// Количество null -- popcount на bitmap
let null_count = array.null_count(); // быстрая побитовая операция
NOTE

Если в массиве нет NULL, validity bitmap может отсутствовать (null_count = 0). Arrow оптимизирует этот случай — нет overhead на bitmap для полностью заполненных колонок.

RecordBatch: группа колонок

RecordBatch объединяет несколько Arrow-массивов (колонок) одинаковой длины под общей схемой:

RecordBatch: 3 колонки, 4 строки
SchemaОписание структуры: имена колонок, типы данных и nullable-флаг для каждого поля
Column: id (Int32)Целочисленная колонка фиксированного размера — 4 байта на значение, доступ за O(1)
Column: name (Utf8)Строковая колонка переменной длины: offset buffer + data buffer
Column: salary (F64)Числовая колонка с nullable: позиция 2 = NULL определяется validity bitmap
row_count: 4 | Каждая колонка — отдельный Arrow Array

RecordBatch — неизменяемая структура. Фильтрация, проекция и другие операции создают новый RecordBatch, ссылающийся на подмножество буферов оригинала (zero-copy slice).

Потоковая обработка в DataFusion

DataFusion обрабатывает данные как поток RecordBatch. Каждый оператор (FilterExec, ProjectionExec, HashJoinExec) получает RecordBatchStream, трансформирует каждый batch и передаёт следующему оператору.

// Концептуальная модель потока
trait RecordBatchStream {
    fn schema(&self) -> SchemaRef;
    async fn next(&mut self) -> Option<Result<RecordBatch>>;
}

Типичный размер batch — 8192 строк. Это баланс между:

  • Достаточно строк для SIMD-векторизации
  • Достаточно мало, чтобы batch помещался в L2 cache (~256 KB)
  • Низкая латентность первого результата (потоковая модель)

Буферы на практике: arrow-rs

В Rust-реализации Arrow (arrow-rs) создание массивов выглядит так:

use arrow::array::{Int32Array, StringArray, RecordBatch};
use arrow::datatypes::{Schema, Field, DataType};
use std::sync::Arc;

// Создание колонок
let ids = Int32Array::from(vec![1, 2, 3, 4]);
let names = StringArray::from(vec!["Иван", "Мария", "Пётр", "Анна"]);
let salaries = Int32Array::from(vec![Some(80000), Some(95000), None, Some(72000)]);

// Схема
let schema = Schema::new(vec![
    Field::new("id", DataType::Int32, false),
    Field::new("name", DataType::Utf8, false),
    Field::new("salary", DataType::Int32, true), // nullable
]);

// RecordBatch
let batch = RecordBatch::try_new(
    Arc::new(schema),
    vec![Arc::new(ids), Arc::new(names), Arc::new(salaries)],
).unwrap();

assert_eq!(batch.num_rows(), 4);
assert_eq!(batch.num_columns(), 3);
TIP

Обратите внимание: Field::new("salary", DataType::Int32, true) — третий аргумент true означает nullable. Arrow валидирует соответствие: если поле non-nullable, а в данных есть NULL, конструктор вернёт ошибку.

Итоги

  • Arrow-массив состоит из буферов: validity bitmap + data buffer (+ offset buffer для строк)
  • Fixed-width типы: два буфера, доступ по индексу за O(1)
  • Variable-width типы: три буфера, offset buffer определяет границы значений
  • NULL — свойство позиции (bitmap), не значение в data buffer
  • RecordBatch = схема + набор колонок одинаковой длины
  • DataFusion обрабатывает данные как поток RecordBatch (~8192 строк на batch)
Arrow memory layout: bit-level

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Из каких буферов состоит Arrow-массив типа Int32?

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

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

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

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