Learning Platform
Глоссарий Troubleshooting
Урок 14.01 · 21 мин
Средний
fault-tolerancetardigradearchitectureinternals

Проблема: stateless-дизайн, сбои воркеров и проект Tardigrade

В модуле про MPP-архитектуру мы установили факт, который теперь надо разобрать всерьёз: по умолчанию Trino не отказоустойчив. Сбой одного воркера посреди запроса роняет весь запрос целиком — и это не баг, а прямое следствие архитектурного решения, которое делает Trino быстрым.

Этот урок — про конфликт между скоростью и надёжностью. Сначала разберём, почему stateless-дизайн делает Trino быстрым, но хрупким; почему для интерактивных запросов это приемлемо, а для долгих batch — нет; и какую идею принёс проект Tardigrade, превратившийся в Fault-Tolerant Execution.


Почему Trino быстрый: stateless и pipelined

Классический Trino исполняет запрос потоково (pipelined). Воркеры читают данные из источника, прогоняют их через цепочку операторов и сразу передают результаты дальше по сети следующей стадии — через exchange. Промежуточные результаты нигде не материализуются целиком: они стримятся через память, как вода по трубам.

Это даёт скорость. Не надо ждать, пока стадия полностью досчитается, прежде чем начать следующую — данные текут конвейером. Не надо писать промежуточные результаты на диск — всё в памяти и сети. Воркеры stateless относительно долговременного состояния: они не хранят чекпойнтов, не ведут журнала прогресса, в любой момент держат лишь то, что прямо сейчас в трубе.

Pipelined-исполнение: данные текут конвейером
Стадия SOURCEВоркеры читают данные из коннектора. Прочитанное сразу уходит дальше, не накапливаясь целиком
exchange (поток)
Стадия JOINСоединение получает данные потоком и потоком же отдаёт результат. Промежуток нигде не материализуется целиком
exchange (поток)
Стадия выводаФинальная стадия отдаёт результат клиенту страницами по мере готовности

Именно так Trino даёт интерактивную скорость: первые строки результата могут прийти ещё до того, как прочитаны все входные данные. Конвейер без материализации — фундамент производительности Trino.


Обратная сторона: почему сбой воркера роняет запрос

У потоковой модели есть жёсткая цена. Раз промежуточные результаты нигде не сохранены, а воркеры stateless — сбой любого воркера невосстановим.

Представь: запрос исполняется на 50 воркерах, отработал уже 40 минут. На 41-й минуте один воркер падает — OOM, сбой железа, его вытеснил Kubernetes, лопнула сеть. Что произошло:

  • Часть данных, обработанная этим воркером, потеряна — она нигде не материализована, восстановить её неоткуда.
  • Воркер не вёл чекпойнтов — неизвестно, сколько именно он успел и где остановился.
  • Другие 49 воркеров зависели от его вывода через exchange — их работа тоже становится бесполезной.

Восстановить нечего и неоткуда. Единственный выход — убить весь запрос и запустить заново с нуля. Сорок минут работы пятидесяти воркеров — впустую из-за падения одного.

WARNING

Это не дефект Trino — это прямое следствие дизайна, который и делает его быстрым. Скорость pipelined-исполнения и хрупкость к сбоям — две стороны одной монеты. Нельзя одновременно «нигде не материализовать промежуток» (ради скорости) и «уметь восстановиться после сбоя» (нужна сохранённая точка для восстановления). Классический Trino осознанно выбрал скорость.


Почему для интерактивных запросов это нормально

Может показаться, что невосстановимость — катастрофа. Но для интерактивных запросов это вполне приемлемо, и вот почему.

Интерактивный запрос длится секунды или минуты. Вероятность, что конкретный воркер упадёт именно за эти полминуты, мала. А если запрос всё же упал — пользователь просто нажимает «выполнить» снова, теряя секунды.

Математика на стороне stateless-дизайна для коротких запросов: накладные расходы отказоустойчивости (материализация промежутка, чекпойнты) пришлось бы платить на каждом запросе, а выигрывали бы лишь те редкие, что наткнулись на сбой. Для интерактивной аналитики «быстро, но иногда перезапусти» — правильный размен.


Где stateless-дизайн ломается: долгие batch-запросы

А теперь — долгий batch-запрос: ETL-трансформация, перестройка большой таблицы, отчёт по терабайтам. Такой запрос идёт часами на десятках воркеров. Здесь арифметика рисков переворачивается.

Чем дольше запрос и чем больше воркеров, тем выше шанс, что хоть один воркер за время исполнения упадёт. Длинное окно уязвимости плюс много единиц, каждая из которых может отказать. И цена падения уже не «нажми кнопку снова»: четырёхчасовой запрос, упавший на третьем часу, означает потерю трёх часов работы кластера — и так по кругу, если не повезёт.

Интерактивный запрос vs долгий batch — разная цена сбоя
Интерактивный: секунды-минуты, мало воркеровМалое окно уязвимости. Шанс наткнуться на сбой воркера низкий. Цена падения — перезапуск за секунды. Stateless-дизайн оправдан
меняем масштаб запроса
Batch: часы, десятки воркеровБольшое окно уязвимости плюс много воркеров, каждый может упасть. Шанс сбоя высокий. Цена падения — потеря часов работы кластера. Stateless-дизайн болезненный

Именно здесь stateless-дизайн становится непригодным. Долгие batch-нагрузки требуют, чтобы запрос переживал сбой отдельного воркера, а не начинал всё заново. Это та же надёжность, что годами давал Spark, ставя её во главу угла, — и долго именно её Trino не имел.

Spark: модель исполнения и устойчивость к сбоям

Проект Tardigrade: идея отказоустойчивости

Чтобы закрыть эту брешь, сообщество Trino запустило проект Tardigrade. Имя выбрано осознанно: тихоходка (tardigrade) — микроскопическое животное, знаменитое способностью выживать в экстремальных условиях. Проект Tardigrade принёс в Trino Fault-Tolerant Execution (FTE) — режим, в котором запрос выживает при сбое воркеров.

Центральная идея FTE — отказаться, когда это нужно, от чистой потоковости в пользу материализации промежуточных данных обмена. Если результат стадии не растворяется в трубе, а спулится (записывается в надёжное хранилище), то после сбоя воркера этот спулённый промежуток можно перечитать и переотправить — другому, живому воркеру. Запрос продолжается с места сбоя, а не с нуля.

Идея FTE: спулинг промежуточных данных обмена
Стадия-продьюсерВоркеры считают стадию. В режиме FTE их вывод не растворяется в потоке
спулинг
Спул промежутка во внешнем хранилищеПромежуточные данные обмена материализуются в надёжное хранилище (filesystem, S3, GCS). Это и есть точка восстановления
чтение, в т.ч. повторное
Стадия-консьюмерСледующая стадия читает промежуток из спула. При сбое воркера данные перечитываются и переотправляются живому воркеру — запрос продолжается

Это размен, зеркальный stateless-дизайну. FTE добавляет накладные расходы — спулинг промежутка стоит I/O и времени, поэтому для коротких интерактивных запросов FTE обычно не нужен. Зато долгий batch-запрос получает то, чего не имел: устойчивость к сбоям, отсутствие перезапуска с нуля. Не «вместо» классического Trino, а режим, который включают там, где надёжность важнее минимальной латентности.

NOTE

FTE — не «магия, чинящая всё». Он покрывает только инфраструктурные сбои: упавший воркер, потерянная нода, сбой железа или сети. Ошибки пользователя FTE не лечит: запрос с синтаксической ошибкой SQL или с делением на ноль не станет «отказоустойчивым» — такой запрос неверен по сути, и повторять его бессмысленно. FTE ретраит инфраструктуру, а не логику.


Что дальше в модуле

Этот урок поставил проблему и назвал идею. Остальной модуль — про то, как FTE устроен и как его эксплуатировать:

  • Retry policies (NONE, QUERY, TASK) — на каком уровне ретраить и когда какой выбрать.
  • Exchange manager — компонент, который спулит промежуток; куда он его кладёт (filesystem, S3, GCS).
  • Task sizing и adaptive planning — как FTE подгоняет размер задач и переоптимизирует план.
  • Эксплуатация — выделенный кластер под TASK, шифрование обмена, ограничения режима.

Держи в голове главный принцип: FTE — это осознанный размен скорости на надёжность через материализацию промежутка. Всё остальное в модуле — детали этого размена.


Попробуй сам

Прочувствуй хрупкость stateless-дизайна на тестовом Trino, ещё без FTE.

  1. Подними кластер с координатором и 2-3 воркерами (docker-compose). Запусти достаточно тяжёлый запрос — например, крупный join по tpch.sf100, который идёт хотя бы минуту.
  2. Пока запрос исполняется, убей один воркер: docker kill <worker-container> или docker stop. Понаблюдай, что запрос немедленно падает — посмотри в CLI текст ошибки и в Web UI статус FAILED.
  3. Прикинь арифметику рисков. Если средняя наработка воркера на отказ — допустим, 30 дней, то какова грубо вероятность сбоя за 10-секундный запрос и за 4-часовой запрос на 50 воркерах? Почему вывод «для интерактива терпимо, для batch — нет» следует из самих чисел?

Цель — увидеть своими глазами, что без FTE сбой одного воркера невосстановим, и понять, для какого класса нагрузок это реальная проблема.


Проверка знанийKnowledge check
Почему классический Trino не может одновременно быть и максимально быстрым (pipelined), и отказоустойчивым к сбоям воркеров, и какой компромисс предложил проект Tardigrade?
ОтветAnswer
Скорость классического Trino держится на pipelined-исполнении: воркеры стримят промежуточные результаты по сети следующей стадии через exchange, нигде не материализуя их целиком — данные текут конвейером через память, как вода по трубам. Это даёт интерактивную скорость, потому что не нужно ждать полного завершения стадии и не нужно писать промежуток на диск. Но отказоустойчивость требует прямо противоположного: чтобы восстановиться после сбоя воркера, нужна сохранённая точка, из которой можно перечитать утраченные данные. Нельзя одновременно нигде не материализовать промежуток (ради скорости) и иметь сохранённую точку восстановления (ради надёжности) — это взаимоисключающие требования, и классический Trino осознанно выбрал скорость. Из-за этого сбой любого воркера невосстановим: его данные нигде не сохранены, чекпойнтов нет, и единственный выход — перезапуск всего запроса с нуля. Проект Tardigrade предложил компромисс — Fault-Tolerant Execution: когда надёжность важнее минимальной латентности, отказаться от чистой потоковости и материализовать промежуточные данные обмена, спулить их в надёжное хранилище. Тогда спулённый промежуток после сбоя воркера можно перечитать и переотправить живому воркеру, и запрос продолжится с места сбоя, а не с нуля. Это режим, добавляющий накладные расходы спулинга ради устойчивости долгих batch-запросов, а не замена классического Trino.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Почему в классическом Trino (без FTE) сбой одного воркера посреди запроса роняет весь запрос целиком?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5