Проблема: stateless-дизайн, сбои воркеров и проект Tardigrade
В модуле про MPP-архитектуру мы установили факт, который теперь надо разобрать всерьёз: по умолчанию Trino не отказоустойчив. Сбой одного воркера посреди запроса роняет весь запрос целиком — и это не баг, а прямое следствие архитектурного решения, которое делает Trino быстрым.
Этот урок — про конфликт между скоростью и надёжностью. Сначала разберём, почему stateless-дизайн делает Trino быстрым, но хрупким; почему для интерактивных запросов это приемлемо, а для долгих batch — нет; и какую идею принёс проект Tardigrade, превратившийся в Fault-Tolerant Execution.
Почему Trino быстрый: stateless и pipelined
Классический Trino исполняет запрос потоково (pipelined). Воркеры читают данные из источника, прогоняют их через цепочку операторов и сразу передают результаты дальше по сети следующей стадии — через exchange. Промежуточные результаты нигде не материализуются целиком: они стримятся через память, как вода по трубам.
Это даёт скорость. Не надо ждать, пока стадия полностью досчитается, прежде чем начать следующую — данные текут конвейером. Не надо писать промежуточные результаты на диск — всё в памяти и сети. Воркеры stateless относительно долговременного состояния: они не хранят чекпойнтов, не ведут журнала прогресса, в любой момент держат лишь то, что прямо сейчас в трубе.
Именно так Trino даёт интерактивную скорость: первые строки результата могут прийти ещё до того, как прочитаны все входные данные. Конвейер без материализации — фундамент производительности Trino.
Обратная сторона: почему сбой воркера роняет запрос
У потоковой модели есть жёсткая цена. Раз промежуточные результаты нигде не сохранены, а воркеры stateless — сбой любого воркера невосстановим.
Представь: запрос исполняется на 50 воркерах, отработал уже 40 минут. На 41-й минуте один воркер падает — OOM, сбой железа, его вытеснил Kubernetes, лопнула сеть. Что произошло:
- Часть данных, обработанная этим воркером, потеряна — она нигде не материализована, восстановить её неоткуда.
- Воркер не вёл чекпойнтов — неизвестно, сколько именно он успел и где остановился.
- Другие 49 воркеров зависели от его вывода через exchange — их работа тоже становится бесполезной.
Восстановить нечего и неоткуда. Единственный выход — убить весь запрос и запустить заново с нуля. Сорок минут работы пятидесяти воркеров — впустую из-за падения одного.
Это не дефект Trino — это прямое следствие дизайна, который и делает его быстрым. Скорость pipelined-исполнения и хрупкость к сбоям — две стороны одной монеты. Нельзя одновременно «нигде не материализовать промежуток» (ради скорости) и «уметь восстановиться после сбоя» (нужна сохранённая точка для восстановления). Классический Trino осознанно выбрал скорость.
Почему для интерактивных запросов это нормально
Может показаться, что невосстановимость — катастрофа. Но для интерактивных запросов это вполне приемлемо, и вот почему.
Интерактивный запрос длится секунды или минуты. Вероятность, что конкретный воркер упадёт именно за эти полминуты, мала. А если запрос всё же упал — пользователь просто нажимает «выполнить» снова, теряя секунды.
Математика на стороне stateless-дизайна для коротких запросов: накладные расходы отказоустойчивости (материализация промежутка, чекпойнты) пришлось бы платить на каждом запросе, а выигрывали бы лишь те редкие, что наткнулись на сбой. Для интерактивной аналитики «быстро, но иногда перезапусти» — правильный размен.
Где stateless-дизайн ломается: долгие batch-запросы
А теперь — долгий batch-запрос: ETL-трансформация, перестройка большой таблицы, отчёт по терабайтам. Такой запрос идёт часами на десятках воркеров. Здесь арифметика рисков переворачивается.
Чем дольше запрос и чем больше воркеров, тем выше шанс, что хоть один воркер за время исполнения упадёт. Длинное окно уязвимости плюс много единиц, каждая из которых может отказать. И цена падения уже не «нажми кнопку снова»: четырёхчасовой запрос, упавший на третьем часу, означает потерю трёх часов работы кластера — и так по кругу, если не повезёт.
Именно здесь stateless-дизайн становится непригодным. Долгие batch-нагрузки требуют, чтобы запрос переживал сбой отдельного воркера, а не начинал всё заново. Это та же надёжность, что годами давал Spark, ставя её во главу угла, — и долго именно её Trino не имел.
Spark: модель исполнения и устойчивость к сбоямПроект Tardigrade: идея отказоустойчивости
Чтобы закрыть эту брешь, сообщество Trino запустило проект Tardigrade. Имя выбрано осознанно: тихоходка (tardigrade) — микроскопическое животное, знаменитое способностью выживать в экстремальных условиях. Проект Tardigrade принёс в Trino Fault-Tolerant Execution (FTE) — режим, в котором запрос выживает при сбое воркеров.
Центральная идея FTE — отказаться, когда это нужно, от чистой потоковости в пользу материализации промежуточных данных обмена. Если результат стадии не растворяется в трубе, а спулится (записывается в надёжное хранилище), то после сбоя воркера этот спулённый промежуток можно перечитать и переотправить — другому, живому воркеру. Запрос продолжается с места сбоя, а не с нуля.
Это размен, зеркальный stateless-дизайну. FTE добавляет накладные расходы — спулинг промежутка стоит I/O и времени, поэтому для коротких интерактивных запросов FTE обычно не нужен. Зато долгий batch-запрос получает то, чего не имел: устойчивость к сбоям, отсутствие перезапуска с нуля. Не «вместо» классического Trino, а режим, который включают там, где надёжность важнее минимальной латентности.
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.
- Подними кластер с координатором и 2-3 воркерами (docker-compose). Запусти достаточно тяжёлый запрос — например, крупный join по
tpch.sf100, который идёт хотя бы минуту. - Пока запрос исполняется, убей один воркер:
docker kill <worker-container>илиdocker stop. Понаблюдай, что запрос немедленно падает — посмотри в CLI текст ошибки и в Web UI статус FAILED. - Прикинь арифметику рисков. Если средняя наработка воркера на отказ — допустим, 30 дней, то какова грубо вероятность сбоя за 10-секундный запрос и за 4-часовой запрос на 50 воркерах? Почему вывод «для интерактива терпимо, для batch — нет» следует из самих чисел?
Цель — увидеть своими глазами, что без FTE сбой одного воркера невосстановим, и понять, для какого класса нагрузок это реальная проблема.