Что такое Trino: распределённый SQL query engine, а не база данных
В прошлом модуле мы уже сформулировали главный тезис: Trino — это движок запросов, а не база данных. Сейчас разберём этот тезис по-настоящему глубоко, потому что от него зависит всё остальное в курсе. Если в голове сидит модель «Trino — это такая база», то почти каждое последующее решение будет пониматься неправильно: почему у Trino нет своих транзакций, почему запрос падает при сбое воркера, почему один и тот же SELECT ведёт себя по-разному на разных коннекторах.
Ключ к правильной модели — идея разделения storage и compute (хранения и вычислений). Этот урок разбирает, что это значит, как Trino реализует только compute-половину, и какие конкретные практические последствия из этого вытекают.
Связанная архитектура классической СУБД
Начнём с того, как устроена обычная база данных, чтобы было от чего отталкиваться. PostgreSQL, MySQL, Oracle — это монолитные системы, где storage и compute сцеплены намертво.
Storage-половина отвечает за то, чтобы данные физически жили на диске: файлы таблиц, страницы, индексы, write-ahead log, механизмы durability. Compute-половина отвечает за исполнение SQL: парсер, планировщик, исполнитель операций. И эти две половины — части одного процесса, они разделяют один формат данных, один диск, одну машину (или один тесно связанный кластер).
У такой связки есть сильные стороны: транзакции, индексы, согласованность работают «из коробки», потому что движок полностью контролирует, как данные лежат на диске. Но есть и жёсткое ограничение. Compute и storage невозможно масштабировать независимо. Если упёрлись в CPU при сложных запросах, но данных немного — всё равно платите за большую машину целиком. Если данных много, но запросов мало — то же самое. И главное: данные заперты внутри. Чтобы запросить их другим движком или соединить с данными из другой системы, их сначала нужно физически вынуть и куда-то перелить.
Trino: только compute
Trino устроен принципиально иначе. Он реализует только compute-половину. Storage-половины у него нет вообще — ни строчки кода, отвечающей за хранение данных на диске. Это сознательное архитектурное решение, а не недоработка.
Trino — это распределённый движок исполнения SQL. Он умеет всё, что относится к compute: разобрать SQL, построить план, распределить работу по кластеру, выполнить джойны и агрегации, применить оптимизации. Но в момент, когда запросу нужны собственно данные, Trino идёт за ними наружу — в storage-системы, которые он не контролирует и которые живут своей жизнью.
Из этой схемы видно, почему Trino называют распределённым SQL query engine. «SQL» — потому что язык общения с ним стандартный ANSI SQL. «Query engine» — потому что это движок запросов и только он. «Распределённый» — потому что compute разнесён по кластеру из множества узлов, работающих параллельно.
Разделение storage и compute — не уникальная идея Trino. По этому же принципу устроены Spark, облачные движки и современные lakehouse-платформы. Trino — один из самых чистых примеров: у него вообще нет своего storage-слоя, тогда как, скажем, Snowflake совмещает облачное хранилище и движок в одном продукте.
Что Trino получает от разделения
Разделение compute и storage — не самоцель. Оно даёт три конкретных преимущества, ради которых Trino и существует.
Независимое масштабирование. Compute (узлы Trino) и storage (объём данных на S3) растут отдельно. Нужно больше вычислительной мощности под пиковую аналитику — добавляете воркеров Trino, не трогая данные. Данных стало больше — растёт только S3, кластер Trino не меняется. Вы платите за то, что реально нужно, а не за монолитную машину «с запасом по обоим параметрам».
Один движок для многих источников. Раз Trino не владеет данными, ему всё равно, откуда они. Один и тот же движок, один и тот же SQL работает поверх Parquet на S3, поверх PostgreSQL, поверх Kafka. Это и есть основа федерации: данные не нужно сводить в одно хранилище, чтобы запросить их вместе.
Открытость данных. Данные не заперты внутри проприетарного формата движка. Они лежат в открытых форматах (Parquet, ORC) и открытых табличных форматах (Iceberg, Delta), к которым имеет доступ не только Trino, но и Spark, и облачные движки, и что угодно ещё. Сменить движок запросов, не трогая данные, — реально.
| Аспект | Монолитная СУБД | Trino (compute отдельно) |
|---|---|---|
| Хранение данных | Внутри СУБД, проприетарный формат | Снаружи, открытые форматы (Parquet/ORC/Iceberg) |
| Масштабирование | Compute и storage вместе | Compute и storage независимо |
| Доступ к данным | Только этой СУБД | Любому движку, понимающему формат |
| Источники в одном запросе | Один (свой) | Много (через коннекторы) |
| Транзакции, индексы | Встроены, полный контроль | Зависят от источника, у движка их нет |
Чем Trino платит за разделение
Честный инженерный разбор требует назвать и обратную сторону. Раз Trino не контролирует storage, он теряет то, что монолитная СУБД получает бесплатно.
Нет своих транзакций уровня СУБД. Trino не управляет durability и изоляцией так, как PostgreSQL. Транзакционные гарантии, какие есть, приходят от табличного формата источника — например, Apache Iceberg даёт атомарность операций на уровне снапшотов. Но это не то же самое, что классические BEGIN/COMMIT с полным набором уровней изоляции.
Нет привычных индексов. Trino не строит и не хранит B-tree индексы — у него нет своего диска, чтобы их держать. Ускорение чтения приходит с другой стороны: партиционирование данных в источнике, статистика для оптимизатора, pushdown фильтров в источник, dynamic filtering. Это другая модель ускорения, и весь курс показывает, как она работает.
Зависимость от источника. Поведение Trino зависит от того, к чему он подключён. Один коннектор поддерживает запись, другой только чтение. Один умеет принять pushdown агрегации, другой нет. Один даёт статистику таблиц, другой — нет. Один и тот же SELECT физически исполняется по-разному в зависимости от коннектора, и понимать это — часть работы инженера.
Сеть как граница. Данные физически находятся не там, где compute. Их нужно передать по сети от storage к узлам Trino. Поэтому Trino так серьёзно относится к pushdown: чем больше работы (фильтрация, проекция, агрегация) удаётся переложить на источник, тем меньше данных едет по сети.
Не ждите от Trino поведения PostgreSQL. Запрос вида «обнови одну строку по первичному ключу» в Trino либо неэффективен, либо вообще не поддержан конкретным коннектором. Trino создан для аналитики поверх больших объёмов, а не для точечных транзакционных операций. Это не баг — это следствие архитектуры.
Trino — это связующий слой
Из разделения compute и storage следует ещё один полезный взгляд на Trino: это не конечная система, а связующий слой между двумя мирами, которые иначе не разговаривают друг с другом.
Row vs Columnar: почему хранение данных разноеС одной стороны — мир хранения данных: объектные хранилища, реляционные базы, потоковые системы, поисковые движки. У каждого свой API, свой формат, свой способ доступа. С другой стороны — мир потребителей данных: аналитики с их SQL, BI-инструменты, дашборды, скрипты. Потребители говорят на одном языке — SQL — и хотят единообразного доступа ко всему.
Trino встаёт ровно между этими мирами. Снизу он через коннекторы умеет работать с разнородными хранилищами, каждое на его собственных условиях. Сверху он предъявляет потребителям один общий интерфейс — стандартный SQL. То есть Trino переводит «много разных способов доступа к данным» в «один SQL поверх всего». Это и делает его таким ценным в зрелой инфраструктуре: он не добавляет ещё одно место хранения, а соединяет уже существующие в единую запрашиваемую поверхность.
Эта роль — «один SQL поверх многих систем» — и есть прямое следствие того, что у Trino нет своего storage. Будь у него своё хранилище, у него был бы соблазн и интерес тянуть данные в себя. Без хранилища у Trino нет другого пути, кроме как быть честным связующим слоем поверх чужих данных. Архитектурное ограничение здесь оборачивается главным преимуществом.
Stateless по своей природе
Ещё одно следствие разделения, которое важно для всего курса: узлы Trino практически stateless. Поскольку данные не принадлежат Trino, а лежат снаружи, узлам нечего хранить между запросами. Узел в любой момент держит только промежуточные данные текущих запросов в оперативной памяти — и ничего постоянного на диске.
Колоночный формат: почему аналитика требует другой памятиЭто даёт Trino отличную горизонтальную масштабируемость и способность обслуживать сотни конкурентных запросов: узлы взаимозаменяемы, добавить или убрать воркера просто. Но у этого есть и цена, которую мы подробно разберём в модуле про MPP-архитектуру: по умолчанию у Trino нет fault tolerance. Если воркер падает посреди запроса, его промежуточные данные в памяти теряются, и запрос целиком завершается с ошибкой. Отказоустойчивость в Trino — отдельный опциональный режим (fault-tolerant execution), а не поведение по умолчанию.
Попробуй сам
Возьмите две системы, с которыми вы уже работали — например, PostgreSQL и Trino — и заполните для каждой простую табличку из четырёх строк: где физически лежат данные; кто контролирует формат данных на диске; можно ли масштабировать compute отдельно от storage; что произойдёт с данными, если убрать сам движок.
Затем сформулируйте письменно: какие три задачи решаются на PostgreSQL лучше, чем на Trino, и какие три — наоборот. Для каждой задачи укажите, какое именно архитектурное свойство (наличие или отсутствие своего storage) делает одну систему лучше другой. Это упражнение закрепит модель «Trino = compute без storage» на конкретных примерах.