Skip to content
Learning Platform

Три движка, три разных вопроса

«Trino, Spark SQL или DuckDB» — это не «какой движок быстрее». Это вопрос «какую проблему вы решаете», маскирующийся под бенчмарк. Три движка дают близкий SQL-синтаксис и читают один и тот же Parquet, но под капотом отвечают на три принципиально разных вопроса об архитектуре данных.

Trino спрашивает: «как опросить десяток разных источников единым SQL, не перетаскивая данные?» Spark SQL спрашивает: «как надёжно прогнать многочасовой ETL по петабайту, переживая падение узлов?» DuckDB спрашивает: «как обсчитать эти данные прямо здесь, в этом процессе, без всякого кластера?»

Ментальная модель, которую нужно держать в голове всю статью: движок выбирают не по объёму данных, а по топологии вычисления — где живут данные, кто их читает, и сколько узлов в этом участвует. Объём — лишь один из входов. Дальше разберём каждый движок до уровня модели памяти и exchange, сведём всё в таблицу осей и разберём контринтуитивный случай, когда ноутбук с DuckDB обгоняет кластер из тридцати воркеров.

Trino: MPP-федерация без своего хранилища

Trino — это MPP-движок, у которого нет собственного storage. Это чистый compute поверх чужих данных. Кластер состоит из coordinator (парсит SQL, строит план, раздаёт работу) и набора workers (исполняют куски запроса).

Ключевая абстракция Trino — Connector SPI. Через коннекторы один SQL-запрос может джойнить таблицу из Iceberg на S3 с таблицей из PostgreSQL и справочником из Kafka — это и есть федерация. Данные не копируются в общее хранилище; Trino тянет их по сети в момент запроса.

Модель исполнения — pipelined exchange. План режется на stages, stage — на tasks по воркерам, task читает свои splits (разбиения исходных данных) и гонит их через операторы (scan, filter, join, aggregation). Между stages данные передаются по сети потоком, не приземляясь на диск. Отсюда низкая latency на интерактивных запросах — и отсюда же главная цена: по умолчанию нет fault tolerance. Упал воркер посреди запроса — запрос упал целиком. Для запроса на секунды это приемлемо: просто перезапустить.

Модель памяти Trino почти полностью in-memory. У каждого запроса есть лимиты query.max-memory-per-node и query.max-memory (на весь кластер). Долгое время Trino при превышении лимита просто убивал запрос с EXCEEDED_LOCAL_MEMORY_LIMIT. Опциональный fault-tolerant execution добавил и отказоустойчивость, и spill промежуточных данных — но это осознанный размен latency на надёжность, не поведение по умолчанию.

Сильная сторона Trino — интерактивная аналитика и федерация. Слабая — он не предназначен для многочасовых трансформаций: нет встроенной материализации между шагами в дефолтном режиме, и долгий запрос рискует упасть от любого сбоя узла. Если хочется увидеть, как coordinator превращает SQL в распределённый план и раздаёт splits воркерам на живом кластере, это разбирается по шагам в бесплатном курсе по Trino с песочницей в браузере — там запрос видно насквозь, от парсинга до exchange между stages.

Spark SQL: distributed compute, который переживает сбои

Spark — это распределённый вычислительный фреймворк, в котором SQL — лишь один из фронтендов (рядом с DataFrame API и произвольным кодом, включая ML). Архитектурно это driver (планирует) и executors (считают), но определяющее отличие от Trino — модель отказоустойчивости через материализацию.

Когда Spark исполняет план, DAGScheduler режет его на стадии по границам shuffle. На каждой границе стадии результат записывается на диск (shuffle files). Это и есть ключ: если executor падает, Spark пересчитывает только потерянные партиции из материализованного предыдущего шага, а не весь job заново. Поэтому Spark — естественный выбор для пятичасового ETL по петабайту, где падение одного из сотни узлов — норма, а не катастрофа.

Эта же материализация — причина, по которой Spark проигрывает Trino по latency на коротких запросах: writing-to-disk между стадиями добавляет накладные расходы, которых pipelined-модель Trino избегает. Современный Spark с Adaptive Query Execution и Tungsten-кодогенерацией сократил разрыв, но архитектурный компромисс остался: надёжность через материализацию против скорости через конвейер.

Модель памяти executor’а делится на execution-память (под shuffle, join, sort, aggregation) и storage-память (под кэш). При нехватке execution-памяти Spark делает spill — сортировка или агрегация частями уходит на локальный диск, job замедляется, но не падает с OutOfMemoryError. Это фундаментальное отличие от дефолтного Trino, который скорее убьёт запрос, чем медленно доработает на диске.

DuckDB: векторизованный single-node, который не нужно разворачивать

DuckDB ломает рамку. Это не кластер и не сервис — это встраиваемая аналитическая база, библиотека внутри вашего процесса (Python, R, CLI), как SQLite, но колоночная и под OLAP. Нет coordinator, нет workers, нет сети, нет деплоя. pip install duckdb — и у вас аналитический движок.

Внутри DuckDB — векторизованный исполнитель, работающий чанками колонок прямо в L1/L2-кэше CPU. Параллелизм — по ядрам через morsel-driven scheduling, а не по узлам. DuckDB читает Parquet, CSV и Iceberg напрямую, в том числе с S3, и умеет push-down проекций и предикатов в Parquet — то есть с диска поднимаются только нужные колонки и только нужные row-groups.

Модель памяти — out-of-core: при SET memory_limit='8GB' DuckDB не упадёт на датасете, который больше RAM, а будет проливать hash-таблицы и сортировки на диск (temp_directory). По духу это ближе к Spark (spill, а не kill), но без всякого распределённого оверхеда.

Цена single-node очевидна: один узел — это потолок по CPU, RAM и сетевой полосе до storage. Нет горизонтального масштабирования и нет отказоустойчивости уровня кластера. Зато нет и кластера, который нужно держать, тюнить и оплачивать.

Когда single-node DuckDB обгоняет кластер

Контринтуитивный, но частый случай: DuckDB на ноутбуке быстрее тридцатинодового Trino/Spark-кластера на том же запросе. Причина — не магия, а устранённый distributed-оверхед. У кластера на каждый запрос есть фиксированная цена: планирование на coordinator/driver, раздача splits/tasks, сетевой shuffle/exchange между узлами, сериализация страниц данных, JVM-разогрев и GC-паузы. На запросе по нескольким десяткам гигабайт эта фиксированная цена доминирует над самим вычислением.

Эмпирическое правило: пока рабочий набор после push-down предикатов и проекций влезает в RAM одного жирного узла (а современный single-node — это легко 128-512 GB), single-node-векторизация почти всегда выигрывает по latency и всегда — по операционной простоте. Знаменитый аргумент в этой плоскости — «большинство аналитических запросов сканируют куда меньше данных, чем кажется»: после column pruning и partition pruning от петабайтной таблицы остаются десятки гигабайт. Кластер начинает реально окупаться, когда рабочий набор стабильно превышает потолок одного узла или когда нужна федерация/конкурентность многих пользователей.

-- DuckDB: тот же Parquet с S3, что и в кластере, но в одном процессе.
-- Predicate + projection push-down: с диска поднимаются
-- только колонки amount, region и только row-groups за 2026 год.
INSTALL httpfs; LOAD httpfs;
SELECT region, sum(amount) AS revenue
FROM read_parquet('s3://lake/sales/year=2026/*.parquet')
WHERE region IN ('EU', 'APAC')
GROUP BY region
ORDER BY revenue DESC;

Тот же запрос в Trino выглядит почти идентично — но за FROM iceberg.sales.fact стоит coordinator, раздающий splits десяткам воркеров, и pipelined exchange между ними. На 50 GB это лишняя работа; на 50 TB с тремя источниками — единственный способ.

Federation против lakehouse

Две архитектурные стратегии, к которым тяготеют движки, стоит развести явно.

Federation — данные остаются в разных системах, движок опрашивает их на месте и джойнит на лету. Это родная территория Trino: один SQL поверх Iceberg + PostgreSQL + Kafka без ETL-копирования. Цена — запрос ограничен самым медленным источником и его сетевой полосой; нет единого контроля над форматом и статистикой.

Lakehouse — данные заранее сложены в открытый табличный формат (lakehouse Iceberg, Delta) на object storage, с ACID-снапшотами и статистикой, а compute-движок поверх — сменный. Здесь все три движка сосуществуют: Spark пишет таблицы в ETL, Trino отдаёт интерактивную аналитику над ними, DuckDB поднимает локальную выборку для дебага. Lakehouse делает выбор движка обратимым: формат — это контракт, движок — деталь реализации.

Практический вывод: в зрелой платформе это обычно не «или-или». Spark строит lakehouse-таблицы (batch, надёжность), Trino федерирует и обслуживает BI поверх них (latency, много источников), DuckDB закрывает локальный анализ и прототипирование без кластера.

Таблица осей: по чему реально выбирать

ОсьDuckDBTrinoSpark SQL
Топологияsingle-node, in-processMPP-кластер (coordinator + workers)distributed (driver + executors)
Масштаб данныхдо RAM/диска одного узла (десятки-сотни GB)TB-PB, но compute-onlyPB, петабайтный batch
Latencyмиллисекунды-секунды (нет сети)секунды (интерактив, pipelined)секунды-часы (материализация)
Основной use-caseлокальный анализ, прототип, embeddedинтерактивная аналитика, BI, федерациятяжёлый ETL, ML, надёжный batch
Модель памятиout-of-core spill на дискin-memory, kill при OOM (FTE даёт spill)execution/storage split, spill на диск
Отказоустойчивостьнет (один процесс)нет по умолчанию (есть в FTE)да, через материализацию shuffle
Setuppip install, ноль инфрыкластер, coordinator, каталогикластер, executors, шафл-сторадж
Федерация источниковограниченная (extensions)первоклассная (Connector SPI)через коннекторы, но не интерактивно

Как читать таблицу: идите сверху вниз по столбцу «топология» и «use-case», а не по «масштаб данных». Сначала ответьте, где живут данные и кто будет считать, и движок отберётся почти автоматически. Объём данных лишь подтверждает или отменяет single-node-вариант.

Решающее дерево в одном абзаце

Данные влезают в RAM одного узла и нужно посчитать здесь-и-сейчас без инфры — DuckDB. Нужна интерактивная аналитика по нескольким источникам или BI поверх lakehouse с latency в секунды — Trino. Нужен надёжный многочасовой ETL по огромным данным, переживающий падение узлов, или ML-пайплайн — Spark SQL. И помните: в реальной платформе все три обычно стоят рядом над одним lakehouse, и выбор для конкретной задачи важнее, чем выбор «навсегда».

Дальше — до железа

Эта статья даёт ментальную модель выбора. Чтобы понять, почему Trino отдаёт результат за секунды — как coordinator строит план, как splits превращаются в drivers и operators, как устроен exchange и колоночный Page/Block-формат до уровня железа — есть наш бесплатный курс по Trino с runnable-песочницей прямо в браузере: пишете SQL, видите stages и tasks вживую.

А чтобы выстроить полную картину профессии — от хранилищ и форматов до движков и оркестрации — посмотрите направление Data Engineering: все курсы бесплатные.

Ещё в направлении · Data Engineering

Все материалы направления →