Massively Parallel Processing: модель shared-nothing
В первом модуле мы выяснили, что Trino — это распределённый SQL query engine, и не раз упоминали, что скорость на больших данных приходит из параллелизма. Пора разобрать, что именно стоит за словом «распределённый». Trino построен по архитектуре MPP — massively parallel processing — на основе модели shared-nothing. Этот урок открывает модуль про устройство кластера, и без него все следующие уроки — про координатор, воркеры, обмен данными — повисли бы в воздухе.
Понять MPP важно потому, что это фундаментальное архитектурное решение Trino. Из него вытекает всё: почему запрос на терабайт данных можно ускорить, просто добавив машин; почему узлы взаимозаменяемы; почему по умолчанию нет fault tolerance; почему кластер тянет сотни конкурентных пользователей. Если в голове есть точная модель MPP, остальное в курсе становится её следствиями.
Проблема: один компьютер не справляется
Начнём с задачи. Нужно выполнить аналитический запрос поверх таблицы на 1 ТБ: просканировать, отфильтровать, сгруппировать, посчитать агрегаты. Один компьютер, даже мощный, упирается здесь в физические пределы. Диск читает данные с ограниченной скоростью. Память конечна — терабайт в неё целиком не положить. Процессор обрабатывает строки с конечной скоростью. Запрос будет идти долго, и купить машину в десять раз быстрее нельзя — таких просто нет, рост производительности одной машины давно замедлился.
Есть два принципиально разных способа нарастить мощность вычислительной системы. Scale up (вертикальное масштабирование) — взять машину больше: больше ядер, больше памяти, более быстрый диск. У этого пути есть жёсткий потолок: самая мощная машина в мире всё равно одна и всё равно конечна. Scale out (горизонтальное масштабирование) — взять много обычных машин и заставить их работать над задачей вместе. У этого пути потолок несравнимо выше: машин можно поставить десять, сто, тысячу.
Trino выбирает scale out. И MPP — это и есть архитектура, которая позволяет много обычных машин превратить в один мощный движок запросов.
Что такое MPP
MPP, massively parallel processing — массово-параллельная обработка — это архитектура, в которой одна задача (запрос) разбивается на множество мелких независимых частей, и эти части исполняются параллельно на множестве узлов кластера.
Слово «massively» здесь не для красоты. Речь не о паре потоков, а о массовом параллелизме: запрос на терабайт может породить тысячи мелких единиц работы, разложенных по десяткам узлов, и на каждом узле — ещё на множество параллельных потоков. Параллелизм идёт сразу на двух уровнях: между узлами кластера и внутри каждого узла между его ядрами.
Логика проста и мощна. Если просканировать 1 ТБ на одной машине занимает, условно, 100 минут, то на 100 машинах, каждая из которых обрабатывает свои 10 ГБ, та же работа в идеале займёт около 1 минуты. Запрос ускоряется примерно во столько раз, во сколько вырос кластер. Это и есть обещание MPP: производительность растёт почти линейно с числом узлов.
Это «оговорённое почти». Линейное ускорение — идеал, к которому MPP стремится, но не всегда достигает: часть работы требует обмена данными между узлами, и этот обмен (network exchange) — тема следующих уроков модуля. Но как ориентир: добавил вдвое больше узлов — получил примерно вдвое быстрее, и это работает на удивление хорошо.
Модель shared-nothing
MPP можно строить по-разному. Trino строит его по модели shared-nothing — «ничего не общего». Это означает: узлы кластера не делят между собой никаких ресурсов. У каждого узла свой процессор, своя оперативная память, свой локальный диск, и узел работает только с ними. Узлы не пишут в общую память и не используют общий диск; единственный способ их взаимодействия — обмен сообщениями по сети.
Противоположность — модели shared: shared-memory (узлы делят общую память) и shared-disk (узлы делят общее хранилище). У этих моделей есть фундаментальная проблема: общий ресурс становится узким местом и точкой конкуренции. Когда сто узлов одновременно лезут в одну память или на один диск, им приходится координировать доступ, выстраиваться в очередь, синхронизироваться. Чем больше узлов, тем сильнее давка вокруг общего ресурса, и масштабирование упирается в потолок.
В shared-nothing этой проблемы нет по построению. Раз общего ресурса нет, нет и давки за него. Каждый узел полностью автономен: получил свой кусок данных — обработал на своём железе — отдал результат. Добавить узел — значит добавить полный комплект независимых ресурсов: ещё один процессор, ещё одну память, ещё один диск. Именно поэтому shared-nothing масштабируется почти линейно, а shared-модели — нет.
| Аспект | Shared-memory / shared-disk | Shared-nothing (Trino) |
|---|---|---|
| Общий ресурс | Память или диск делятся всеми узлами | Ничего общего, каждому узлу — свой комплект |
| Узкое место | Конкуренция за общий ресурс | Нет общего ресурса — нет конкуренции |
| Масштабирование | Упирается в потолок при росте узлов | Почти линейное при добавлении узлов |
| Взаимодействие узлов | Через общий ресурс | Только обмен сообщениями по сети |
Shared-nothing — не изобретение Trino. По этой модели построены практически все современные распределённые аналитические системы: Spark, аналитические MPP-хранилища, многие облачные движки. Термин ввёл ещё в 1986 году Майкл Стоунбрейкер. Trino — чистая и современная реализация этой давно проверенной идеи.
Что shared-nothing даёт Trino
Из выбора shared-nothing-MPP вытекают сразу несколько ключевых свойств Trino, которые мы будем разбирать дальше в курсе.
Горизонтальная масштабируемость. Не хватает мощности — добавляете воркеров. Каждый новый воркер — это полный комплект независимых ресурсов, и кластер становится быстрее почти линейно. Стало меньше нагрузки — убираете воркеров. Это эластичность, которой нет у одной машины.
Взаимозаменяемость узлов. Раз узлы автономны и не хранят ничего уникального, они одинаковы и заменяемы. Любой воркер может взять любой кусок работы. Это сильно упрощает эксплуатацию: добавление и вывод узлов — рутинная операция, а не перестройка системы.
Высокая конкурентность. Кластер shared-nothing способен обслуживать сотни одновременных запросов от множества пользователей: работа дробится на мелкие независимые части и раскладывается по всему пулу узлов.
Но у медали есть и обратная сторона, и честный разбор требует её назвать. Поскольку узлы автономны и держат промежуточные данные запроса только в своей оперативной памяти, выход узла из строя посреди запроса означает потерю его части работы. По умолчанию это приводит к падению всего запроса — отсюда отсутствие fault tolerance, о котором мы уже упоминали. Это прямое следствие модели, и ему посвящён последний урок модуля.
Где у MPP пределы
Чтобы не идеализировать, обозначим границы. MPP не делает любой запрос быстрее в N раз на N узлах. Линейность нарушается, когда задача требует, чтобы данные с разных узлов встретились друг с другом — например, при джойне или глобальной агрегации. Тогда узлам приходится перераспределять данные между собой по сети, а сеть медленнее локальной памяти и сама может стать узким местом. Кроме того, у любого запроса есть часть, которую невозможно распараллелить (скажем, финальная сборка единого результата), и эта часть ограничивает максимальное ускорение. Trino спроектирован так, чтобы минимизировать сетевой обмен и неделимые участки — как именно, мы разберём в уроках про exchange и про распределённое исполнение. Пока достаточно понимать: MPP даёт огромный, но не бесконечный и не идеально линейный выигрыш.
Попробуй сам
Возьмите конкретную задачу: просканировать таблицу на 2 ТБ и посчитать count(*) с фильтром. Прикиньте на бумаге, сколько времени это займёт на 1 узле, на 10 узлах и на 100 узлах, если один узел обрабатывает данные со скоростью, условно, 1 ГБ за 5 секунд. Запишите три числа и убедитесь, что видите линейный характер ускорения.
Затем подумайте над вторым вопросом: что изменится, если запрос — не просто count, а джойн двух больших таблиц, и данным с разных узлов нужно встретиться по ключу джойна. Почему здесь идеальная линейность нарушится? Сформулируйте письменно своими словами, какой именно ресурс становится узким местом — это подготовит вас к уроку про exchange.