Таксономия: stage, task, split, driver, operator
Модуль про жизненный цикл закончился на distributed planning — план разрезан на дерево stages. Теперь начинается исполнение. И здесь нужна предельная точность в терминах. Слова stage, task, split, driver, operator звучат похоже, их легко перепутать — но они обозначают пять разных уровней, и спутать их значит не понять, как Trino исполняет запрос вообще.
Этот урок — фундамент всего модуля. Он даёт точные определения пяти уровней и, главное, точные связи между ними. Каждый следующий урок модуля разворачивает один из уровней вглубь; здесь — карта целиком.
Пять уровней: общая картина
Распределённое исполнение запроса в Trino описывается иерархией из пяти уровней. От самого крупного, концептуального, к самому мелкому, физическому, это: stage (концептуальная фаза плана), task (исполнение стадии на воркере), split (кусок входных данных), driver (физическая цепочка операторов, наименьший уровень параллелизма) и operator (одна операция над данными). Полную диаграмму со всеми связями мы соберём в конце урока, когда разберём каждый уровень.
Самая частая и самая вредная ошибка новичка — считать, что stage «выполняется на воркере». Это неверно, и весь модуль рассыпется, если этого не зафиксировать. Разберём каждый уровень с упором именно на связи.
Stage: концептуальная единица плана
Stage (стадия) — это фаза распределённого плана. На прошлом модуле мы видели, как distributed planning разрезает план на фрагменты, и каждому фрагменту соответствует stage. Стадии образуют дерево: корневая стадия наверху агрегирует вывод дочерних.
И вот ключевое.
Stage — это КОНЦЕПТУАЛЬНАЯ единица распределённого плана. Stage сам по себе НЕ исполняется напрямую на воркерах. Stage — это описание: “вот такую фазу плана нужно выполнить, распределив её по кластеру”. У stage нет своего процесса, своего потока, своего куска памяти на воркере. Stage существует как модель — на coordinator, который по этой модели управляет исполнением. Когда говорят “stage обрабатывает данные” — это сокращение; данные обрабатывают task этой стадии.
Зачем тогда stage нужна, если она не исполняется. Stage — это единица планирования и координации. Coordinator оперирует деревом stages, чтобы понимать структуру запроса: какая фаза от какой зависит, какая ждёт данных от какой. Это уровень, на котором запрос рассуждается как целое. Но превращение фазы в реальную работу — это уже следующий уровень.
Task: исполнение стадии на воркере
Task (задача) — это runtime-реализация стадии на конкретном воркере. Вот task — это уже не концепция, а реальная исполняемая единица: у неё есть процесс исполнения на воркере, своя память, своё состояние.
Связь stage и task — это связь «один ко многим», и она центральная для всего модуля:
Одна stage распараллеливается на множество task — по одной (или больше) на каждый задействованный воркер. Stage говорит «надо прочитать таблицу customer и посчитать частичный агрегат»; task — это «воркер B прямо сейчас читает свою часть customer и считает». Stage — чертёж фазы, task — фаза, воплощённая на конкретной машине.
Именно task — то, что вы видите работающим в Web UI и в EXPLAIN ANALYZE, когда там показано распределение нагрузки по нодам. Coordinator создаёт task, рассылает их воркерам, следит за их состоянием. Запрос завершён, когда завершены все его task.
Сформулируем разницу предельно чётко, потому что это половина смысла урока:
| Stage | Task | |
|---|---|---|
| Что это | Концептуальная фаза плана | Runtime-исполнение фазы |
| Где существует | Как модель на coordinator | Реально работает на воркере |
| Сколько | Одна на фрагмент плана | Много на одну stage |
| Исполняется ли | Нет, это описание | Да, это исполнение |
Split: кусок данных для задачи
Split — это секция (кусок) большого набора данных, над которой работает задача. Мы встречали split в модуле про коннекторы: SplitManager разбивает таблицу на splits, чтобы её можно было читать параллельно.
В таксономии исполнения split — это «порция работы» для task. Task source-стадии (читающей данные) получает один или несколько splits и обрабатывает их. Splits source-стадий приходят от коннектора; splits промежуточных стадий — от вышестоящих по дереву стадий.
Связь «task — split» — снова один ко многим: одна task обрабатывает один или несколько splits. Если таблица разбита на 1000 splits, а воркеров 10, то splits распределятся между task этих воркеров — каждая task получит примерно по 100 splits и переварит их.
Driver: наименьший уровень параллелизма
Driver (драйвер) — это последовательность экземпляров операторов, физический набор операторов в памяти. У драйвера один вход и один выход — данные входят с одного конца цепочки и выходят с другого.
И вот второе критически важное определение модуля.
Driver — это НАИМЕНЬШИЙ уровень параллелизма в Trino. Не task, а именно driver. Одна task исполняется параллельно множеством драйверов. Каждый driver — это отдельная физическая цепочка операторов, обрабатывающая данные независимо от других драйверов той же задачи. Когда мы говорим, что Trino распараллеливает работу, самая мелкая единица этого параллелизма — driver.
То есть параллелизм в Trino многоуровневый, и важно видеть оба уровня:
- Между воркерами: одна stage -> много task на разных воркерах. Это параллелизм по машинам.
- Внутри воркера: одна task -> много drivers. Это параллелизм по ядрам внутри одной машины.
Driver обрабатывает данные, прогоняя их через свою цепочку операторов. Несколько драйверов одной задачи работают одновременно на разных ядрах воркера, каждый над своей порцией данных. Поэтому реальный параллелизм запроса — это число task, умноженное на число drivers в каждой.
Operator: одна операция над данными
Operator (оператор) — это компонент, который потребляет, трансформирует и производит данные. Оператор — самый мелкий уровень: одна конкретная операция.
Примеры операторов: table scan (читает данные из коннектора), filter (применяет предикат), project (вычисляет выражения), aggregation (агрегирует), join (соединяет), exchange (обменивается данными с другими стадиями). По сути операторы — это исполняющие двойники тех PlanNode, которые мы видели в логическом плане: FilterNode плана при исполнении становится filter-оператором.
Операторы не существуют сами по себе — они выстроены в цепочку внутри driver. Driver — это и есть последовательность операторов: данные входят в первый оператор цепочки, проходят через каждый по очереди, выходят из последнего. Один оператор берёт результат предыдущего как свой вход.
Вся иерархия в одной картине
Соберём пять уровней и все связи между ними:
Прочитаем эту иерархию одной фразой, и пусть она станет якорной для всего модуля: distributed planning производит дерево stages — это концепция; каждая stage разворачивается во множество task на воркерах — это исполнение; каждая task обрабатывает splits — куски данных — силами множества drivers — а driver это наименьшая единица параллелизма, физическая цепочка operators — отдельных операций над данными.
Три связи здесь несут весь смысл: stage и task — это «концепция против исполнения»; task и driver — это «один ко многим, параллелизм внутри воркера»; driver и operator — это «цепочка и её звено». Кто держит в голове эти три связи, тот понимает распределённое исполнение Trino.
Запомните три утверждения-маяка. Первое: stage НЕ исполняется на воркере — исполняется task. Второе: driver, а не task, — наименьший уровень параллелизма. Третье: реальный параллелизм запроса = (число task) умножить на (число drivers в каждой task). Если эти три фразы прочно усвоены, остальные уроки модуля лягут на готовый каркас.
Попробуй сам
Таксономия видна в EXPLAIN ANALYZE и в Web UI:
- Выполните
EXPLAIN ANALYZE SELECT nationkey, count(*) FROM tpch.sf10.customer GROUP BY nationkey. В выводе фрагменты — это stages. Найдите для каждого фрагмента упоминание числа task или нод — это уровень task. - Откройте этот же запрос в Web UI на странице деталей. Найдите дерево стадий, а внутри стадии — счётчики задач (tasks) и драйверов (drivers, иногда в составе pipeline-статистики).
- Для одного фрагмента посмотрите, сколько строк он обработал и на сколько task это разложилось — оцените, какая нагрузка пришлась на одну task.
- Напишите своими словами три связи: stage-task, task-driver, driver-operator. Для каждой укажите тип связи (один-к-одному, один-ко-многим) и в чём её смысл.