Learning Platform
Глоссарий Troubleshooting
Урок 13.06 · 22 мин
Средний
cpu-limitstimeoutsconfigurationoperations

CPU-лимиты, query.max-cpu-time и тайм-ауты запросов

Память — не единственный исчерпаемый ресурс кластера. Запрос может уложиться во все лимиты памяти, но при этом сжигать процессорное время часами: декартово произведение по недосмотру, regexp по миллиардам строк, забытый аналитический запрос. Один такой запрос отбирает CPU у всех остальных и тихо деградирует кластер.

Trino даёт два класса защиты по времени. Первый — CPU-лимиты: потолок процессорного времени, которое запросу позволено сжечь. Второй — тайм-ауты: предельная продолжительность разных фаз жизни запроса по «настенным часам». Это разные вещи: CPU-время и реальное время — не одно и то же. Разберём оба и почему по умолчанию они почти не ограничивают.


CPU-время против настенного времени

Сначала — фундаментальное различие, без которого CPU-лимиты не понять.

Настенное время (wall-clock time) — реальное время от старта до финиша запроса по часам на стене. Если запрос шёл 5 минут — это 5 минут настенного времени.

CPU-время — суммарное процессорное время, потраченное на запрос на всех ядрах всех воркеров. Запрос распараллелен: за 5 минут настенного времени он мог занять 200 ядер, и тогда CPU-время — около 1000 минут (200 ядер x 5 минут).

Настенное время vs CPU-время — разные величины
Настенное время = 5 минутРеальное время от старта до финиша по часам. Что видит пользователь, ожидая результат
запрос шёл на 200 ядрах параллельно
CPU-время = около 1000 минутСумма процессорного времени по всем ядрам всех воркеров: 200 ядер x 5 минут. Это и есть нагрузка запроса на кластер

Почему лимитировать именно CPU-время? Потому что CPU-время и есть реальная цена запроса для кластера. Запрос, который висит 2 часа, ожидая медленный внешний источник, почти не потребляет CPU — он не вредит соседям, он просто ждёт. А запрос, который за 10 минут сжёг 5000 ядро-минут на декартовом произведении, отобрал процессор у всех остальных. query.max-cpu-time ловит именно второй случай — прожорливость по процессору, а не долгое ожидание.


query.max-cpu-time и почему дефолт — практическая бесконечность

query.max-cpu-time ограничивает суммарное CPU-время одного запроса по всему кластеру.

Дефолт — практически бесконечность. Внутри это значение 1_000_000_000d — на практике запрос никогда столько не накопит. То есть по умолчанию CPU-время запроса не ограничено вообще.

Почему так? Потому что разумный потолок CPU-времени невозможно задать универсально. Для интерактивного кластера 10 ядро-минут — уже подозрительно много. Для batch-кластера, который ночами пережёвывает терабайты, 5000 ядро-минут на один ETL-запрос — норма. Дефолт «бесконечность» означает: Trino не угадывает за тебя, ограничение CPU-времени — осознанное решение администратора под конкретный кластер.

# etc/config.properties (координатор)
# Потолок суммарного CPU-времени одного запроса по кластеру.
# Дефолт практически бесконечен — задаётся осознанно под кластер.
# Пример для интерактивного кластера:
query.max-cpu-time=2h

Здесь 2h — это два часа CPU-времени, а не настенного. На кластере с сотнями ядер такой запрос исчерпает лимит за считанные минуты настенного времени, если действительно грузит процессор на полную.

Превышение query.max-cpu-time -> запрос убивается с ошибкой EXCEEDED_CPU_LIMIT. Текст ошибки прямо называет лимит:

Query 20260520_143012_00031_b7m2q failed:
Query exceeded maximum CPU time limit of 2.00h
[EXCEEDED_CPU_LIMIT]
NOTE

CPU-лимит существует на двух уровнях. query.max-cpu-time — глобальный потолок для всех запросов кластера. А внутри resource groups (урок 5) есть softCpuLimit и hardCpuLimit — лимиты CPU-времени для конкретной группы за период. Глобальный лимит ловит совсем уж аномальные запросы; групповые лимиты дают тонкую настройку под нагрузки. Их можно и нужно использовать вместе.


Тайм-ауты: ограничение по настенному времени

CPU-лимит ловит прожорливость. Но запрос может вредить и иначе — просто слишком долго жить: висеть полдня, занимая слот resource group, держа память, мешая планированию. Против этого — тайм-ауты по настенному времени.

Ключевые тайм-ауты Trino:

СвойствоЧто ограничивает
query.max-run-timeПолную продолжительность запроса от submit до завершения (настенное время)
query.max-execution-timeВремя именно фазы исполнения (без учёта ожидания клиента за результатом)
query.client.timeoutСколько запрос живёт, не получая обращений от клиента, прежде чем будет прерван
query.min-expire-ageСколько информация о завершённом запросе хранится для UI/API

Разберём смысл главных.

query.max-run-time — общий потолок жизни запроса. Запрос, идущий дольше, убивается, как бы он ни был занят. По умолчанию величина большая (порядка суток) — Trino не обрывает долгие легитимные batch-запросы без явного указания.

query.max-execution-time — тоньше. Жизнь запроса включает не только исполнение, но и ожидание, пока клиент заберёт результат. Trino отдаёт результат страницами: клиент опрашивает nextUri и забирает страницы. Если клиент медленный или приостановился, запрос формально «жив», но не исполняется — он ждёт клиента. query.max-execution-time ограничивает именно исполнительную часть, не наказывая запрос за медлительность клиента.

Жизнь запроса: исполнение и ожидание клиента — разные фазы
SubmitКлиент отправил SQL координатору, запрос принят и поставлен в работу
фаза исполнения
ИсполнениеКоординатор и воркеры реально считают запрос. Эту фазу ограничивает query.max-execution-time
фаза отдачи результата
Ожидание клиентаРезультат готов, запрос ждёт, пока клиент заберёт страницы через nextUri. query.max-execution-time эту фазу НЕ считает, а query.max-run-time — считает

query.client.timeout решает проблему брошенных запросов. Клиент Trino обязан периодически опрашивать координатор (poll по nextUri). Если клиент исчез — упал, потерял сеть, пользователь закрыл ноутбук, — запрос остался бы висеть, занимая ресурсы зря. query.client.timeout задаёт, как долго запрос терпит молчание клиента: не дождавшись обращений за этот срок, координатор прерывает запрос и освобождает ресурсы.


Зачем тайм-аутов так много

Может показаться избыточным иметь четыре разных тайм-аута. Но каждый закрывает свой класс проблем, и подменять их друг другом нельзя.

ПроблемаКакой механизм её ловит
Запрос сжигает слишком много процессораquery.max-cpu-time (CPU-время)
Запрос в принципе живёт слишком долгоquery.max-run-time (настенное время, вся жизнь)
Запрос слишком долго именно считаетсяquery.max-execution-time (настенное время, только исполнение)
Клиент исчез, запрос-сирота виситquery.client.timeout (молчание клиента)
Незавершённая инфо о запросах копится в памяти координатораquery.min-expire-age (срок хранения метаданных)

Разные оси: CPU-время против настенного, вся жизнь против фазы исполнения, активность против молчания клиента. Один тайм-аут все эти случаи не покроет — потому их и несколько.

WARNING

Точные имена и дефолты тайм-аутов между релизами Trino меняются — список свойств в admin/properties для версии 481 не обязан совпадать с более старыми гайдами. Перед настройкой продакшен-кластера всегда сверяй имена и значения со страницей свойств той версии, которую разворачиваешь, а не полагайся на статью трёхлетней давности.


Как это сочетается с остальной защитой ресурсов

Соберём всю картину управления ресурсами из модуля воедино. У Trino получается несколько слоёв, и каждый закрывает свою ось:

  1. Лимиты памяти (query.max-memory и соседи) — сколько данных запрос держит в RAM.
  2. Spilling — аварийный клапан: при нехватке RAM выгрузить состояние на диск.
  3. Low-memory killer — последний предохранитель: разорвать памятный дедлок, убив одну жертву.
  4. Resource groups — изоляция нагрузок: поделить слоты, память и CPU между категориями запросов.
  5. CPU-лимиты (query.max-cpu-time, softCpuLimit/hardCpuLimit групп) — сколько процессора запрос вправе сжечь.
  6. Тайм-ауты — сколько по настенному времени запрос вправе жить и ждать.

Память, процессор, время, изоляция — четыре независимые оси. Грамотно настроенный кластер использует все слои: тогда ни один отдельный запрос — будь он прожорлив по памяти, по CPU или просто бесконечно долгий — не способен деградировать кластер для остальных.


Попробуй сам

Поэкспериментируй с CPU-лимитом и тайм-аутами на тестовом Trino.

  1. Задай в etc/config.properties маленький query.max-cpu-time, например 30s, перезапусти Trino. Запусти заведомо тяжёлый по CPU запрос — крупное соединение без условия (фактически cross join) по tpch.sf100 — и поймай ошибку EXCEEDED_CPU_LIMIT. Сравни в Web UI настенное время этого запроса и его CPU-время: они будут сильно различаться.
  2. Задай небольшой query.max-run-time и запусти долгий запрос — посмотри, как он обрывается по настенному времени, даже будучи занятым.
  3. Поразмышляй: у тебя интерактивный дашбордный кластер и отдельный ночной batch-кластер. Какие значения query.max-cpu-time и тайм-аутов ты задашь каждому и почему они должны различаться?

Цель — закрепить, что CPU-время и настенное время — разные величины, и подобрать осмысленные лимиты под профиль кластера.


Проверка знанийKnowledge check
Почему дефолт query.max-cpu-time — практическая бесконечность, и чем CPU-лимит принципиально отличается от тайм-аута по настенному времени вроде query.max-run-time?
ОтветAnswer
Дефолт query.max-cpu-time практически бесконечен (внутри — значение 1_000_000_000d), потому что разумный потолок CPU-времени невозможно задать универсально. Для интерактивного кластера запрос на 10 ядро-минут уже подозрителен, а для batch-кластера, перемалывающего ночами терабайты, 5000 ядро-минут на один ETL-запрос — норма. Trino не угадывает за администратора: ограничение CPU-времени — осознанное решение под конкретный профиль кластера. Принципиальное отличие от тайм-аута по настенному времени в том, что это разные величины. CPU-время — суммарное процессорное время по всем ядрам всех воркеров: распараллеленный запрос за 5 минут настенного времени мог занять 200 ядер и сжечь около 1000 ядро-минут. Настенное время — реальная длительность по часам на стене. query.max-cpu-time ловит прожорливость по процессору: запрос, отбирающий CPU у соседей, например декартово произведение. А query.max-run-time ловит другое — запрос, который просто слишком долго живёт по настенным часам, даже если CPU почти не потребляет: например, висит полдня, ожидая медленный внешний источник, занимая слот resource group и память. Запрос может быть невинным по CPU, но вредным по продолжительности, и наоборот, поэтому нужны оба механизма — они закрывают разные оси.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Запрос шёл 5 минут настенного времени, исполняясь параллельно на 200 ядрах. Чему примерно равно его CPU-время?

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

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

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

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