Learning Platform
Глоссарий Troubleshooting
Урок 07.01 · 20 мин
Средний
Vectorized ExecutionBlockIColumnSIMDCPU CacheColumn Batching

Векторизованное выполнение

Почему ClickHouse обрабатывает миллиарды строк в секунду на одном сервере? Ответ начинается не с индексов и не с сжатия — он начинается с модели выполнения. ClickHouse использует векторизованное (batch) выполнение: все операторы работают не на отдельных строках, а на пакетах значений столбцов (column blocks).

Операторы работают на пакетах (векторах) значений столбцов. Обработка данных порциями кардинально снижает накладные расходы и улучшает использование CPU-кэша.


Block: единица данных в памяти

Block — это контейнер, представляющий подмножество таблицы в оперативной памяти. Каждый Block — это набор троек:

КомпонентТипНазначение
IColumnМассив значенийНепрерывный массив данных одного столбца (UInt64, String и т.д.)
IDataTypeМетаданные типаОписывает как сериализовать, десериализовать и сравнивать значения
column_nameСтрокаИмя столбца для привязки к схеме таблицы

Block обычно содержит до 65 536 строк (настраивается через max_block_size). Именно Block передаётся между процессорами в pipeline — не строки, не файлы, не целые таблицы.


Строчное vs. Векторизованное выполнение

Классические СУБД (PostgreSQL, MySQL) обрабатывают данные по одной строке: каждая строка проходит через цепочку операторов, каждый оператор вызывается для каждой строки отдельно. При миллиардах строк накладные расходы на вызов функций, проверки типов и переключения контекста становятся доминирующими.

ClickHouse инвертирует этот подход: каждый оператор получает целый блок (тысячи значений одного столбца) и обрабатывает его за один вызов функции.

Строчная обработка vs. Векторизованная обработка
Row-by-row: sum() x 1M вызововСтрочная модель: каждая строка проходит через оператор отдельно. При 1 миллионе строк -- 1 миллион вызовов функции sum(). Каждый вызов несёт накладные расходы: проверка типа, virtual dispatch, branch prediction miss.
Overhead: type check + virtual call на строкуНакладные расходы на строку: виртуальный вызов метода, проверка NULL, декодирование типа, обновление агрегатного состояния. При миллиарде строк -- миллиард таких циклов.
РезультатМедленно: CPU тратит время на overheadВ строчной модели до 90% процессорного времени может уходить на интерпретацию, а не на полезные вычисления. Это называется interpretation overhead.
Block: sum() на 65K значений за 1 вызовВекторизованная модель: оператор получает блок из 65 536 значений Int64 как непрерывный массив. Один вызов функции обрабатывает весь массив за один проход через tight loop.
Tight loop + SIMD + cache localityTight loop по непрерывному массиву: компилятор применяет SIMD-автовекторизацию, данные помещаются в L1/L2 кэш процессора, branch prediction стабилен. Один вызов функции вместо 65 536.
РезультатБыстро: CPU тратит время на данныеВ векторизованной модели более 90% процессорного времени уходит на полезные вычисления. Overhead амортизирован: один вызов функции на 65 536 строк вместо 65 536 вызовов.

Почему это работает: три механизма

1. Амортизация накладных расходов

Каждый вызов функции несёт фиксированные расходы: подготовка аргументов, virtual dispatch, проверки. В строчной модели эти расходы на каждую строку. В блочной модели — на каждый блок (65 536 строк). Экономия: 65 536x меньше вызовов.

2. CPU-кэш и data locality

IColumn хранит значения как непрерывный массив в памяти: [val_0, val_1, val_2, ..., val_65535]. Когда процессор загружает cache line (64 байта), он получает сразу 8 значений UInt64 или 16 значений UInt32. Следующее значение уже в кэше — нет cache miss.

В строчной модели столбцы разных типов перемешаны в одной строке. Cache line содержит фрагменты нескольких столбцов, но запрос читает только один — остальные байты потрачены впустую.

3. SIMD-инструкции

SIMD (Single Instruction, Multiple Data) — набор процессорных инструкций, обрабатывающих несколько значений за один такт. Пример: AVX-512 обрабатывает 8 значений Float64 параллельно.

Компилятор автоматически векторизует tight loops по непрерывным массивам. ClickHouse также использует SIMD вручную в критических путях:

// Упрощённый пример: суммирование блока UInt64
// Компилятор превращает этот loop в SIMD-инструкции
UInt64 sum = 0;
for (size_t i = 0; i < block_size; ++i)
    sum += data[i];

При block_size = 65 536 и AVX-512: вместо 65 536 сложений — ~8 192 SIMD-операции (8 значений за раз).


Block в контексте pipeline

Данные в ClickHouse движутся через pipeline процессоров. Каждый процессор получает Block на вход и выдаёт Block на выход:

ReadFromMergeTree → Block → ExpressionTransform → Block → AggregatingTransform → Block → Output

Ключевые свойства:

  • Block передаётся по значению между процессорами (zero-copy через shared pointers на IColumn)
  • Размер блока контролируется настройкой max_block_size (по умолчанию 65 536)
  • Параллелизм: несколько потоков обрабатывают разные Block одновременно (число потоков = max_threads)

Практические следствия для пользователя

АспектСледствие
Размер блокаmax_block_size влияет на потребление памяти и эффективность SIMD. Обычно 65 536 оптимально
Количество столбцов в SELECTКаждый столбец — отдельный IColumn в Block. SELECT * загружает все столбцы в память
Типы данныхФиксированные типы (UInt64, Float64) эффективнее для SIMD, чем String (переменная длина)
Параллелизмmax_threads определяет сколько Block обрабатываются одновременно
TIP

Проверить настройки блока текущей сессии:

SELECT
    name, value, description
FROM system.settings
WHERE name IN ('max_block_size', 'max_threads')

Ключевые выводы

  1. Block — это набор троек (IColumn, IDataType, column_name), представляющий подмножество таблицы в памяти. Все процессоры pipeline работают с Block.
  2. Амортизация: один вызов функции на 65 536 строк вместо 65 536 отдельных вызовов. Overhead сокращается на порядки.
  3. CPU cache locality: непрерывный массив значений одного столбца максимально эффективно использует cache line процессора.
  4. SIMD: компилятор автоматически векторизует tight loops по непрерывным массивам. AVX-512 обрабатывает до 8 Float64 за такт.
  5. Для пользователя: max_block_size и max_threads — два ключевых параметра, влияющих на производительность vectorized execution.
Whole-Stage CodeGen в Spark: фузия операторов Apache Arrow: колоночная память и SIMD

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 3. Что представляет собой Block в модели выполнения ClickHouse?

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

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

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

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