Векторные аналитические СУБД: архитектура DuckDB
В предыдущих темах мы познакомились с языком аналитического SQL и колоночным форматом Parquet. Традиционный подход к аналитической обработке данных требовал развёртывания тяжёлых распределённых кластеров (Apache Spark, Presto/Trino, ClickHouse) или клиент-серверных СУБД. Однако для подавляющего большинства аналитических задач (наборы данных от мегабайтов до сотен гигабайт) администрирование распределённого кластера создаёт неоправданные инфраструктурные затраты.
С 2019 года в индустрии произошёл фундаментальный сдвиг благодаря появлению концепции встраиваемой аналитической СУБД (In-Process OLAP), лидером которой стала система DuckDB, разработанная в Центре математики и информатики в Амстердаме (CWI) Марком Раасвельдтом и Ханнесом Мюлейзеном 1. DuckDB часто называют «SQLite для аналитики»: система работает как легковесная библиотека прямо в адресном пространстве процесса приложения, не требует отдельного сервера и обеспечивает феноменальную скорость выполнения SQL-запросов 2.
В этой теме мы разберём внутреннее устройство современных векторизованных движков на примере архитектуры DuckDB: векторную обработку в памяти, многопоточное планирование Morsel-Driven Parallelism, работу с внешними форматами без промежуточного копирования (Zero-Copy Data Virtualization) и возможности расширенного аналитического SQL.
Векторизованная модель выполнения (Vectorized Execution)
Классические реляционные СУБД (PostgreSQL, Oracle, MySQL) используют модель выполнения Volcano (итераторную модель кортежей, Tuple-at-a-Time), предложенную Гётцем Грефе 3. В этой модели каждый узел дерева плана вызывает метод next() у дочернего узла и получает ровно одну строку:
[ HashAggregate ]
^
| next() -> строка 1
[ Filter: val > 100 ]
^
| next() -> строка 1
[ TableScan ]
При обработке миллионов строк эта модель создаёт огромные накладные расходы:
- миллионы виртуальных вызовов функций (указатели на функции);
- постоянные промахи предсказателя переходов процессора (Branch Misprediction);
- неэффективное использование кэша команд (Instruction Cache) и регистров CPU.
Векторизация: обработка массивов векторов (Vector-at-a-Time)
Векторизованная модель (предложенная в системе MonetDB/X100 Питером Бончем 4) кардинально меняет этот принцип: метод next() возвращает не одну строку, а вектор значений (типичный размер вектора в DuckDB — 2048 элементов, помещающихся в кэш данных L1/L2 процессора):
[ HashAggregate ]
^
| next() -> Вектор [2048 значений]
[ Filter: val > 100 ]
^
| next() -> Вектор [2048 значений]
[ TableScan ]
Внутри узла плана операция фильтрации или вычисления превращается в плотный цикл по непрерывному массиву памяти:
// Упрощённая иллюстрация векторизованного фильтра:
for (int i = 0; i < VECTOR_SIZE; i++) {
selection_vector[count] = i;
count += (data[i] > 100);
}
Компилятор разворачивает такой цикл с использованием векторных инструкций процессора (SIMD: SSE4.2, AVX2, AVX-512), обрабатывая по 8–16 чисел за один машинный такт.
Параллелизм на основе порций (Morsel-Driven Parallelism)
DuckDB автоматически масштабирует выполнение запроса на все доступные ядра процессора без создания сложных распределённых блокировок. В основе планировщика лежит концепция Morsel-Driven Parallelism, предложенная исследователями Мюнхенского технического университета для СУБД HyPer 5.
- Вся обрабатываемая таблица или набор файлов Parquet логически разбивается на небольшие независимые «порции» (morsels) фиксированного размера (обычно около 100 000 строк).
- Создаётся пул рабочих потоков (по числу физических ядер CPU).
- Потоки динамически забирают очередную порцию из глобальной очереди задач, полностью обрабатывают её в локальной памяти ядра (включая построение локальных хеш-таблиц) и объединяют результаты на финальной стадии слияния.
- Если одно ядро процессора временно занято операционной системой, другие ядра продолжают разбирать порции, гарантируя идеальную балансировку нагрузки без простоя.
Виртуализация данных и прямое чтение файлов (Zero-Copy Engine)
Одной из самых сильных сторон DuckDB является способность выполнять полноценный аналитический SQL по внешним файлам и другим СУБД без предварительного импорта:
-- Прямой запрос к CSV и Parquet с объединением на лету:
SELECT d.district_name,
count(t.incident_id) AS total_incidents,
avg(p.consumption) AS avg_power
FROM read_parquet('s3://smart-city-lake/telemetry/*.parquet') p
JOIN read_csv('districts.csv') d ON d.district_id = p.district_id
LEFT JOIN read_json('incidents.json') t ON t.sensor_id = p.sensor_id
GROUP BY d.district_name;
DuckDB выступает в роли универсального аналитического процессора:
- автоматический вывод схемы (Schema Inference) для JSON и CSV;
- чтение файлов по маске (
*.parquet, разбиение по каталогамyear=2024/month=03/*); - прямое подключение к базам данных SQLite и PostgreSQL через сканеры памяти без промежуточной выгрузки на диск;
- бесшовная интеграция с Python DataFrame (Pandas, Polars, PyArrow) через стандарт Apache Arrow с нулевым копированием.
Продвинутые возможности SQL для анализа телеметрии
DuckDB существенно расширяет стандартный SQL, делая работу с аналитикой лаконичной и выразительной:
- Предложения
COLUMNS()и модификаторыEXCLUDE/REPLACE:-- Выбрать все столбцы, исключая громоздкие описания: SELECT * EXCLUDE (debug_payload, raw_hex) FROM sensors; -- Применить агрегатную функцию сразу ко всем числовым столбцам: SELECT avg(COLUMNS(* EXCLUDE (sensor_id, recorded_at))) FROM sensors; - Оконные функции над скользящими интервалами времени:
-- Расчёт скользящего среднего значения за 3 предшествующих часа: SELECT sensor_id, recorded_at, val, avg(val) OVER ( PARTITION BY sensor_id ORDER BY recorded_at RANGE BETWEEN INTERVAL '3 HOURS' PRECEDING AND CURRENT ROW ) AS rolling_avg_3h FROM telemetry; - Конструкция
ASOF JOIN(сопоставление по ближайшему времени): При анализе IoT-данных показания разных датчиков приходят несинхронно.ASOF JOINпозволяет соединить запись с ближайшим по времени предыдущим событием без громоздких подзапросов:SELECT r.timestamp, r.sensor_id, r.val, a.alert_status FROM readings r ASOF JOIN alerts a ON r.sensor_id = a.sensor_id AND r.timestamp >= a.alert_time;
Итоги темы
DuckDB объединила в себе удобство автономной библиотеки и архитектурные решения передовых аналитических систем: векторизованное ядро, планировщик Morsel-driven, глубокую интеграцию с колоночными форматами Parquet и Arrow. Это позволяет аналитикам и инженерам данных выполнять сложнейшие исследования сотен гигабайт телеметрии на рабочей станции за считанные секунды, не прибегая к развёртыванию тяжёлых серверных кластеров.
Литература
- Raasveldt M., Mühleisen H. DuckDB: an Embeddable Analytical Database. — Proceedings of the 2019 ACM SIGMOD International Conference on Management of Data, 2019, С. 1981–1984 1.
- DuckDB Foundation. DuckDB Documentation: In-Process Analytical SQL Engine. — 2024. https://duckdb.org/docs/ 2.
- Graefe G. Volcano — An Extensible and Efficient Query Evaluation System. — IEEE Transactions on Knowledge and Data Engineering, 1994, Vol. 6, No. 1, С. 120–135 3.
- Boncz P. A., Zukowski M., Nes N. MonetDB/X100: Hyper-Pipelining Query Execution. — CIDR, 2005, Vol. 5, С. 225–237 4.
- Leis V., Boncz P., Kemper A., Neumann T. Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework for the Many-Core Age. — Proceedings of the 2014 ACM SIGMOD International Conference on Management of Data, 2014, С. 743–754 5.