Coordinator: парсинг, планирование, управление воркерами
В прошлом уроке мы установили, что Trino — это MPP-кластер по модели shared-nothing: много автономных узлов работают над запросом вместе. Но «работают вместе» не значит «без управления». В оркестре много музыкантов, но есть дирижёр. В кластере Trino эту роль играет coordinator — координатор.
Кластер Trino состоит из узлов двух ролей: один координатор и ноль или более воркеров. Этот урок целиком про координатор: что это за узел, какие обязанности он несёт и почему именно он — точка входа в кластер. Следующий урок будет про воркеров. Разделять эти роли важно: путаница «координатор обрабатывает данные» — одно из самых частых заблуждений про Trino, и сейчас мы его развеем.
Координатор — это мозг кластера
Coordinator — это сервер Trino, отвечающий за приём SQL-запросов от клиентов, их разбор и планирование, а также за управление воркерами. Если воркеры — это «руки» кластера, которые двигают данные, то координатор — это «мозг», который думает и раздаёт указания.
В кластере координатор ровно один. Это единственная точка входа: все клиенты — Trino CLI, JDBC-драйвер, BI-инструменты, Python-клиент — подключаются именно к координатору и общаются только с ним. Воркеров клиент не видит и напрямую к ним не обращается.
Узел становится координатором через файл конфигурации etc/config.properties. Ключевая строка:
coordinator=true
http-server.http.port=8080
discovery.uri=http://trino-coordinator:8080
coordinator=true объявляет узел координатором. http-server.http.port — порт, на котором координатор принимает и клиентов, и служебные запросы. discovery.uri — адрес, по которому воркеры будут находить координатор (об этом — в уроке про discovery service). У воркера в этом же файле стояло бы coordinator=false.
Что делает координатор: четыре обязанности
Разберём работу координатора по обязанностям. Их удобно свести к четырём.
Обязанность 1. Приём запроса. Клиент присылает координатору текст SQL-запроса по HTTP. С точки зрения клиента это просто строка. Координатор регистрирует новый запрос, присваивает ему уникальный идентификатор (query id) и берёт на себя весь его жизненный цикл.
Обязанность 2. Парсинг и анализ. Координатор разбирает текст SQL. Парсер превращает строку в дерево синтаксиса (AST). Затем идёт семантический анализ: координатор проверяет, что упомянутые каталоги, схемы, таблицы и столбцы существуют, что типы согласованы, что запрос вообще корректен. Если в запросе опечатка в имени таблицы или несовместимые типы — именно координатор это поймает и вернёт клиенту ошибку, ни один воркер при этом не задействуется.
Обязанность 3. Планирование. Это интеллектуальное ядро работы координатора. Из проверенного запроса он строит план исполнения. Сначала логический план — дерево операций (какие сканы, фильтры, джойны, агрегации нужны). Затем оптимизатор улучшает этот план. Затем план превращается в распределённый: разбивается на части — stages (стадии) — так, чтобы их можно было разложить по кластеру и исполнять параллельно. Весь конвейер «текст SQL -> распределённый план» — это работа координатора, и ему посвящён отдельный модуль курса (жизненный цикл запроса). Здесь важно зафиксировать: планирование целиком делает координатор.
Обязанность 4. Управление исполнением. Готовый распределённый план координатор раздаёт воркерам: создаёт на них задачи (tasks), отслеживает их выполнение, координирует, передаёт воркерам служебную информацию. Воркеры считают, координатор дирижирует: следит за прогрессом, реагирует на завершение и сбои, собирает у воркеров финальный результат и отдаёт его клиенту.
Чего координатор не делает: он не обрабатывает данные
Теперь — критически важный пункт, на котором спотыкаются многие. Координатор по умолчанию не обрабатывает данные сам. Он не читает таблицы из источников, не выполняет фильтры и джойны над строками. Это работа воркеров. Координатор думает и управляет, но не двигает данные.
Это прямое следствие разделения обязанностей. Планирование и управление — задача, по своей природе централизованная: должен быть один узел, который видит весь запрос целиком и принимает решения. Обработка данных — задача, по своей природе параллельная: её надо раздробить и разложить по многим узлам. Свести две такие разные задачи на один узел было бы плохой архитектурой: координатор стал бы и узким местом планирования, и не справлялся бы с объёмом данных. Поэтому роли разведены: координатор — единственный и централизованный, воркеры — многочисленные и параллельные.
Заблуждение «координатор обрабатывает данные наравне с воркерами» — одно из самых частых. По умолчанию это не так: координатор занят парсингом, планированием и управлением, а данные двигают воркеры. Если на координаторе тяжело — почти всегда дело в нагрузке планирования и координации (много запросов, сложные планы), а не в обработке данных.
Есть один нюанс, который стоит знать, чтобы не путаться. Существует настройка node-scheduler.include-coordinator. Если её включить, координатор дополнительно начинает работать ещё и как воркер — то есть брать на себя обработку данных. В маленьких инсталляциях и для обучения (вспомните одиночный Docker-контейнер из модуля 0, где координатор и воркер совмещены) это удобно. Но это именно опциональное расширение роли. В нормальном продакшен-кластере координатор оставляют выделенным под его прямые обязанности, чтобы планирование и координация не конкурировали за ресурсы с обработкой данных.
Почему координатор один
Раз координатор — единственная точка входа и централизованный «мозг», возникает вопрос: не опасно ли это? Не становится ли он узким местом и единой точкой отказа?
Координатор один по необходимости. Планирование требует целостной картины: один узел должен видеть весь запрос, всю статистику, всё состояние кластера, чтобы принять согласованные решения. Размазать планирование по нескольким равноправным координаторам — значит получить проблему их синхронизации между собой, что сложно и дорого. Поэтому в стандартной архитектуре Trino координатор один.
При этом узким местом он на практике становится редко. Координатор не обрабатывает данные — самую тяжёлую часть работы, — а лишь планирует и дирижирует. Один координатор спокойно управляет кластером из десятков воркеров и сотнями конкурентных запросов. Когда координатору всё же тяжело, лечат это не делением роли, а вертикальным усилением узла координатора и тюнингом, а на уровне нескольких кластеров — отдельным продуктом Trino Gateway, который мы упомянем позже в курсе.
Полезно понимать, что нагружает координатор, а что нет. Тяжёлая для координатора работа — это планирование сложных запросов (много джойнов, много таблиц, большие планы) и одновременное управление множеством запросов. Эта нагрузка масштабируется с числом и сложностью запросов, а не с объёмом данных. То есть кластер может перемалывать петабайты, и координатору при этом будет легко, пока запросы простые и их немного. И наоборот: даже на скромных данных шквал сложных конкурентных запросов способен загрузить координатор. Отсюда практический вывод: когда координатор перегружен, смотреть надо на профиль запросов и их параллелизм, а наращивание воркеров здесь не поможет — воркеры разгружают обработку данных, а не планирование.
Что касается единой точки отказа: да, если узел координатора выходит из строя, кластер перестаёт принимать и вести запросы — воркеры без дирижёра работать не могут. В стандартной архитектуре Trino это принимается как данность, а на практике смягчается надёжностью самого узла координатора и быстрым перезапуском. На уровне нескольких кластеров перед ними ставят Trino Gateway, который умеет направлять трафик на здоровые кластеры; это тема отдельного модуля курса.
| Свойство | Координатор | Воркер |
|---|---|---|
| Сколько в кластере | Ровно один | Ноль или более |
| Точка входа для клиентов | Да | Нет |
| Парсинг и анализ SQL | Да | Нет |
| Планирование запроса | Да | Нет |
| Обработка данных | Нет (по умолчанию) | Да |
| Управление другими узлами | Да | Нет |
Минимальный работающий кластер Trino — это один координатор и ноль воркеров. В такой конфигурации координатору, чтобы вообще что-то исполнить, обычно включают node-scheduler.include-coordinator, и он совмещает обе роли. Именно так устроен одиночный Docker-контейнер из урока про среду разработки.
Попробуй сам
Запустите свой локальный Trino из урока про среду разработки и через CLI выполните запрос к системному каталогу:
SELECT node_id, http_uri, node_version, coordinator
FROM system.runtime.nodes;
Этот запрос возвращает все узлы кластера. Посмотрите на столбец coordinator: значение true — это узел-координатор. В одиночном контейнере вы увидите одну строку, и она будет координатором.
Затем откройте Web UI координатора на http://localhost:8080, запустите любой запрос к tpch.tiny и пронаблюдайте за ним в интерфейсе. Подумайте и запишите: какие из четырёх обязанностей координатора (приём, анализ, планирование, управление) вы фактически наблюдаете в Web UI и в какой момент. Это свяжет теорию урока с тем, что реально происходит в кластере.