Learning Platform
Глоссарий Troubleshooting
Урок 02.04 · 22 мин
Средний
olapoltpworkloadarchitecture

OLAP против OLTP: почему DuckDB не конкурент PostgreSQL/SQLite

«Какая СУБД быстрее — DuckDB или PostgreSQL?» — вопрос, который звучит разумно, но на самом деле поставлен неверно. Это всё равно что спросить, что быстрее — грузовик или спорткар. Ответ зависит от задачи: на гоночном треке выиграет спорткар, при перевозке тонны груза — грузовик. Не потому, что один «лучше» в абсолютном смысле, а потому что они спроектированы под разные задачи и оптимизированы под разные цели.

С СУБД так же. DuckDB и PostgreSQL/SQLite спроектированы под два разных класса нагрузки — OLAP и OLTP. Почти каждое инженерное решение внутри движка вытекает из того, под какой класс он создан. Поэтому грамотный инженер сначала определяет класс своей нагрузки, и только потом выбирает движок. Перепутать порядок — выбрать движок, а потом подгонять под него задачу — типичная и дорогая ошибка.

В этом уроке мы строго разграничим OLAP и OLTP, проследим, как класс нагрузки определяет устройство движка, и поймём, почему DuckDB и PostgreSQL — не конкуренты, а инструменты для разных задач.

Стоит сразу сказать, почему это не теоретическое упражнение. Выбор СУБД — одно из самых дорогих в исправлении решений в проекте: данные накапливаются, код привязывается к движку, и сменить СУБД позже больно. Ошибиться легко: «возьмём DuckDB, он быстрый» или «возьмём PostgreSQL, он надёжный» — оба рассуждения игнорируют главное, класс нагрузки. Умение с первого взгляда отнести задачу к OLTP или OLAP экономит проекту месяцы переделок. Поэтому разграничение из этого урока — практический навык, а не определения для запоминания.


Два класса нагрузки

OLTP — Online Transaction Processing, обработка транзакций. Это нагрузка приложений. Пользователь нажал «купить» — приложение вставляет строку в orders, обновляет остаток в inventory, читает профиль пользователя по user_id. Признаки OLTP-нагрузки:

  • Очень много операций, каждая — короткая.
  • Каждая операция трогает мало строк: одну-несколько, обычно по первичному ключу.
  • Операции работают со строкой целиком: вставить всю запись, прочитать все поля одной записи.
  • Много операций идёт одновременно (тысячи пользователей).
  • Смесь чтений и записей; записи критичны и должны быть надёжными.

OLAP — Online Analytical Processing, аналитическая обработка. Это нагрузка аналитики и отчётности. «Выручка по регионам за квартал», «топ-10 товаров по марже», «динамика подписок по месяцам». Признаки OLAP-нагрузки:

  • Запросов мало, каждый — тяжёлый и долгий.
  • Каждый запрос сканирует много строк: миллионы, иногда всю таблицу.
  • Запрос трогает мало колонок из многих: посчитать SUM(amount) по таблице из 50 колонок.
  • Конкурентность низкая: несколько аналитиков, не тысячи.
  • В основном чтения; запись — пакетная загрузка, редкая.
OLTP против OLAP: форма нагрузки
OLTPМного коротких операций по первичному ключу, каждая трогает одну строку целиком; высокая конкурентность
трогает
Одна строка, все колонкиТранзакция читает или пишет целую запись со всеми полями: профиль пользователя, заказ
OLAPМало запросов, каждый тяжёлый: сканирует миллионы строк; низкая конкурентность
трогает
Много строк, мало колонокАналитический запрос агрегирует миллионы строк по двум-трём колонкам из многих

Обратите внимание: формы нагрузки почти зеркальны. OLTP — узкий по строкам (мало строк), полный по колонкам (вся строка). OLAP — широкий по строкам (миллионы), частичный по колонкам (две-три из многих). Движок нельзя одинаково хорошо оптимизировать под обе — приходится выбирать. Полезно держать в голове и происхождение терминов: «transaction» в OLTP — это бизнес-транзакция приложения (оформить заказ, перевести деньги), отсюда упор на надёжную запись малых порций; «analytical» в OLAP — это анализ накопленных данных ради выводов и отчётов, отсюда упор на быстрое чтение больших объёмов. Сами названия классов уже подсказывают, под что заточен движок каждого класса.


Как класс нагрузки определяет устройство движка

ACID на пальцах: что именно обещает СУБД ClickHouse: анатомия Part как единицы хранения OLAP

Проследим три ключевых архитектурных решения и увидим, что каждое прямо вытекает из класса нагрузки.

Раскладка данных: row-store или columnar. OLTP трогает строку целиком — выгодно хранить поля строки рядом (row-store): одна операция чтения с диска поднимает всю запись. OLAP трогает мало колонок по всем строкам — выгодно хранить колонку непрерывным массивом (columnar): движок читает только нужные колонки. PostgreSQL и SQLite — row-store (под OLTP). DuckDB — columnar (под OLAP).

Модель исполнения: построчная или векторизованная. OLTP трогает мало строк — накладные расходы на строку незаметны, построчный Volcano-движок в самый раз. OLAP трогает миллионы строк — накладные расходы на строку умножаются на миллионы, нужна векторизация (об этом был прошлый урок). PostgreSQL/SQLite — построчные. DuckDB — векторизованный.

Модель конкурентности. OLTP — это тысячи одновременных транзакций, многие из них пишут; движку нужна сложная блокировочная и транзакционная машинерия для высококонкурентной записи. OLAP — несколько аналитиков, в основном читают; достаточно модели «один писатель, много читателей». PostgreSQL рассчитан на высококонкурентную запись. DuckDB — на «один писатель, много читателей» в одном процессе.

Класс нагрузки определяет архитектуру
OLTP-нагрузкаМного коротких транзакций, строка целиком, высокая конкурентная запись
диктует
Row-store, построчный движок, конкурентная записьАрхитектура PostgreSQL и SQLite — оптимизирована под транзакции
OLAP-нагрузкаМало тяжёлых запросов, мало колонок из многих, низкая конкурентность
диктует
Columnar, векторизованный движок, один писательАрхитектура DuckDB — оптимизирована под аналитику

Вывод важный: это не значит, что разработчики PostgreSQL «не додумались» до columnar-хранения, а разработчики DuckDB «не осилили» конкурентную запись. Это осознанный выбор. Оптимизация под один класс нагрузки делает движок хуже на другом. Универсального движка, одинаково сильного и в OLTP, и в OLAP, не существует — потому что требования зеркально противоположны.

Разберём ещё один пример того, как класс нагрузки тянет архитектуру — индексы. В OLTP-движке индексы критичны: транзакция ищет одну строку по ключу, и без индекса пришлось бы сканировать всю таблицу ради одной записи. Поэтому PostgreSQL и SQLite вкладываются в развитые B-tree индексы, и схема OLTP-базы обычно увешана индексами. В OLAP всё иначе: аналитический запрос и так читает миллионы строк, точечный поиск ему не нужен — ему нужно быстро прогнать большой объём. Поэтому DuckDB не делает ставку на индексы в OLTP-смысле; вместо этого он опирается на zonemaps — компактную статистику min/max по блокам данных, которая позволяет пропускать целые блоки, заведомо не подходящие под фильтр. Это другой инструмент под другую задачу: B-tree индекс быстро находит иголку в стоге сена, zonemap быстро отбрасывает части стога, где иголки точно нет. Снова — не «лучше или хуже», а «под свой класс нагрузки». Устройство zonemaps курс подробно разбирает в модуле про storage-формат.

То же касается и обращения с записью. OLTP-движок оптимизирует мелкую частую запись отдельных строк — это его хлеб. OLAP-движок оптимизирован под пакетную загрузку: эффективнее всего DuckDB вставляет данные большими порциями, а не по строчке. Это прямое следствие колоночного хранения: дописать одну строку означает тронуть все колоночные сегменты, а вот загрузить сразу миллион строк — выгодно, потому что каждый сегмент заполняется крупным куском. Поэтому в аналитических пайплайнах данные и грузят пакетами, и это не ограничение, а соответствие инструмента его классу нагрузки.


Что значит «не конкуренты» на практике

Раз DuckDB и PostgreSQL/SQLite оптимизированы под разные классы, сравнивать их «в лоб» бессмысленно. Корректно так: на OLTP-нагрузке выиграет PostgreSQL, на OLAP-нагрузке выиграет DuckDB. Каждый проигрывает на чужом поле — by design.

Покажем это на конкретике. Пусть в PostgreSQL есть таблица заказов с 50 колонками.

OLTP-запрос — типичная операция приложения:

-- OLTP: получить один заказ по ключу. Поле движка: PostgreSQL
SELECT * FROM orders WHERE order_id = 894213;

Здесь PostgreSQL силён: по индексу первичного ключа он за миллисекунды находит одну строку, и row-store отдаёт все 50 её полей одним чтением. DuckDB на этом запросе будет медленнее: columnar-раскладка не даёт преимущества (нужна одна строка, но все колонки), а для точечного поиска по ключу его движок не оптимизирован.

OLAP-запрос — типичная операция аналитики:

-- OLAP: агрегат по всем строкам, две колонки из 50. Поле движка: DuckDB
SELECT region, SUM(amount) AS revenue
FROM orders
GROUP BY region
ORDER BY revenue DESC;

Здесь силён DuckDB: columnar-хранение читает с диска только region и amount, остальные 48 колонок не трогаются; векторизованный движок прогоняет миллионы значений пачками с SIMD. PostgreSQL на этом запросе медленнее: row-store вынужден прочитать все строки целиком со всеми 50 полями, а построчный движок платит накладные расходы за каждую из миллионов строк.

ЗапросOLTP-движок (PostgreSQL/SQLite)OLAP-движок (DuckDB)
WHERE order_id = ?Быстро: индекс + row-storeМедленнее: не его профиль
GROUP BY ... SUM(...) по всем строкамМедленнее: row-store + построчноБыстро: columnar + векторизация
Тысячи конкурентных вставокСправляется: рассчитан на этоАнти-паттерн: один писатель
Пакетная загрузка и аналитикаРаботает, но не профильПрофиль: для этого создан
WARNING

Самый частый анти-паттерн с DuckDB — использовать его как операционную БД веб-приложения с высококонкурентной записью. Это OLTP-нагрузка, и DuckDB на ней проигрывает — не из-за «незрелости», а из-за модели «один писатель, много читателей». Для backend приложения берите PostgreSQL или SQLite. Анти-паттернам DuckDB посвящён следующий урок.


Типичная архитектура: они дополняют друг друга

На практике OLTP- и OLAP-движки не конкурируют, а сосуществуют в одной системе, потому что у бизнеса есть оба класса нагрузки.

Приложение работает с операционной БД — PostgreSQL: принимает заказы, обновляет остатки, обслуживает пользователей. Это OLTP. Параллельно аналитикам нужны отчёты — выручка, динамика, сегменты. Гонять тяжёлые аналитические запросы прямо по операционной PostgreSQL вредно: они конкурируют за ресурсы с транзакциями приложения и тормозят его. Поэтому данные периодически выгружают для аналитики, и аналитические запросы исполняет OLAP-движок — DuckDB. Это OLAP.

DuckDB здесь не «вместо» PostgreSQL — он рядом, для другого класса задач. PostgreSQL делает то, для чего создан (транзакции приложения), DuckDB — то, для чего создан (аналитика). Именно в этом смысл фразы «не конкуренты»: они закрывают разные половины потребностей системы. Грамотный инженер не выбирает «или-или» — он понимает, какой класс нагрузки перед ним сейчас, и берёт правильный из двух инструментов.


Почему нельзя «просто сделать движок быстрым на всём»

Естественный вопрос: если требования OLTP и OLAP противоположны, почему бы не построить движок, который умеет и то и другое — переключаясь по ситуации? Попытки гибридных (HTAP — Hybrid Transactional/Analytical Processing) систем существуют, но универсального решения без компромиссов нет, и причина — глубже, чем «не успели сделать».

Возьмём одну-единственную развилку — раскладку данных на диске. Данные физически лежат в памяти линейно, и разложить их можно либо построчно, либо поколоночно — третьего варианта, одинаково хорошего для обоих, не существует. Построчная раскладка делает быстрой сборку целой строки (нужно OLTP) и медленным чтение одной колонки по всем строкам (нужно OLAP). Поколоночная — наоборот. Любой выбор раскладки уже определяет, на каком классе нагрузки движок силён. Можно хранить данные в двух раскладках сразу — но это удвоение объёма и стоимость синхронизации при каждой записи, то есть снова компромисс, просто перенесённый в другое место.

То же касается модели исполнения, модели конкурентности, устройства индексов — каждое решение тянет движок в сторону одного класса. Гибридная система вынуждена либо выбрать сторону и проигрывать на другой, либо нести двойную стоимость. Поэтому на практике победил не «один универсальный движок», а специализация: отдельный движок под OLTP, отдельный под OLAP, и оба в одной системе под свои половины нагрузки. DuckDB — продукт этой логики специализации: он не пытается быть всем, он предельно хорош на OLAP и честно отдаёт OLTP другим движкам. Понимание, что специализация — это сила, а не ограниченность, и есть главный вывод урока.


Попробуй сам

Натренируйте классификацию нагрузок — это базовый навык при выборе СУБД.

Для каждой задачи определите класс (OLTP или OLAP) и подходящий движок (PostgreSQL/SQLite или DuckDB):

  1. Веб-магазин: при оформлении заказа вставить строку и обновить остаток товара.
  2. Еженедельный отчёт: средний чек по категориям за последний год по таблице на 200 миллионов строк.
  3. Мобильное приложение-заметки: хранить заметки локально, читать и обновлять их по одной.
  4. Дашборд: пересчитать топ-100 страниц по просмотрам из 500 миллионов событий.
  5. Сервис аутентификации: проверять логин/пароль по user_id тысячи раз в секунду.

Ответы: 1 — OLTP, PostgreSQL/SQLite. 2 — OLAP, DuckDB. 3 — OLTP, SQLite. 4 — OLAP, DuckDB. 5 — OLTP, PostgreSQL/SQLite. Сформулируйте по каждому пункту, какой признак нагрузки (число строк, число колонок, конкурентность) стал решающим. Этот разбор и есть практическое применение урока.


Проверка знанийKnowledge check
Почему вопрос «что быстрее, DuckDB или PostgreSQL» поставлен неверно, и как класс нагрузки определяет, какой движок выиграет?
ОтветAnswer
Вопрос неверен, потому что DuckDB и PostgreSQL спроектированы под два разных класса нагрузки и оптимизированы зеркально противоположно. OLTP-нагрузка (транзакции приложения) состоит из множества коротких операций, каждая трогает мало строк по первичному ключу и работает со строкой целиком, при высокой конкурентной записи. OLAP-нагрузка (аналитика) — это мало тяжёлых запросов, каждый сканирует миллионы строк, но трогает мало колонок из многих, при низкой конкурентности. Класс нагрузки диктует архитектуру: под OLTP выгодны row-store, построчный движок и машинерия конкурентной записи (так устроены PostgreSQL и SQLite); под OLAP выгодны columnar-хранение, векторизованный движок и модель «один писатель, много читателей» (так устроен DuckDB). Оптимизация под один класс ухудшает движок на другом — универсального не бывает. Поэтому корректный ответ зависит от нагрузки: на точечном запросе по ключу и конкурентной записи выиграет PostgreSQL, на агрегации миллионов строк по нескольким колонкам выиграет DuckDB. Каждый проигрывает на чужом поле by design, и грамотный инженер сначала определяет класс своей нагрузки, а потом выбирает движок.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Какая комбинация признаков характеризует именно OLAP-нагрузку?

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

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

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

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