Trino memory management: почему запрос упал с OutOfMemory
Управление памятью в Trino: query.max-memory, query.max-memory-per-node, memory pools. Почему падает QUERY_EXCEEDED_LOCA
Если ваш Trino периодически падает с QUERY_EXCEEDED_LOCAL_MEMORY_LIMIT, а в логах лишь размытое «Query exceeded per-node memory limit», вы не одиноки. Эта ошибка — классическая грабля, на которую наступают почти все. Понимание, как Trino управляет памятью, не просто спасёт от падений, но и позволит выжать из кластера максимум производительности без хаотичного увеличения ресурсов. Давайте разбираться с памятью системно.
Как Trino распределяет память
Вся память в Trino делится на пулы (memory pools). Два ключевых — General Pool и Reserved Pool. Reserved Pool — это аварийный механизм для спасения «самого прожорливого» запроса, когда в General Pool всё кончилось. В основном же работа идёт в General Pool. Ваша задача — корректно ограничить потребление запросов, чтобы они не пытались сожрать весь этот пул.
Контроль происходит через два главных параметра в config.properties:
query.max-memory-per-node: Максимальный объём памяти (например, 4GB), который один запрос может использовать на одном worker узле.query.max-memory: Максимальный объём памяти (например, 20GB), который один запрос может использовать на всём кластере (сумма по всем worker узлам).
# Настройки на worker узлах
query.max-memory-per-node=4GB
query.max-total-memory-per-node=8GB
# Настройка на coordinator
query.max-memory=20GB
Важно: query.max-total-memory-per-node — это лимит всей памяти, которую узел может выделить под все выполняемые на нём задачи одного запроса. Обычно его делают в 1.5-2 раза больше query.max-memory-per-node.
Почему падает QUERY_EXCEEDED_LOCAL_MEMORY_LIMIT
Эта ошибка возникает, когда операция (сортировка, хэш-джоин, агрегация) на конкретном worker узле пытается выделить больше, чем query.max-memory-per-node (или query.max-total-memory-per-node). Частые причины:
- Неравномерное распределение данных (data skew): Один ключ джоина содержит 10 миллионов строк, а остальные — по сотне. Задача, обрабатывающая этот «тяжёлый» ключ, требует гигантской хэш-таблицы и падает.
- Запрос без ограничений:
SELECT * FROM huge_table ORDER BY columnпытается отсортировать всю таблицу сразу в памяти. - Некорректные настройки: Лимиты
query.max-memory-per-nodeустановлены без учёта доступной памяти JVM и размера General Pool.
Практическая настройка под реальную нагрузку
-
Рассчитайте базовые лимиты. На worker узле выделите JVM ~80% от физической RAM. Из этой кучи под General Pool обычно отдают 40-60%. Например, для сервера с 64GB RAM и Xmx=50G, можно задать:
query.max-memory-per-node=12GB query.max-total-memory-per-node=24GBЛимит на весь кластер
query.max-memoryдолжен быть не меньше, чемquery.max-memory-per-node * количество worker узлов, но часто его делают меньше, чтобы оставить запас для параллельного выполнения других запросов. -
Мониторьте и анализируйте. Используйте веб-интерфейс Trino (
/ui/query.html). Смотрите на пиковое распределение памяти (Peak User Memory) у упавших и медленных запросов. Это ваш главный источник истины. -
Боритесь с data skew. Используйте
JOINна распределённых ключах с равномерной дистрибуцией. Иногда помогает увеличениеquery.max-memory-per-nodeкак временное решение, но это лишь маскировка проблемы. -
Настройте spooling на диск. Для операций сортировки и джоинов можно разрешить сброс промежуточных данных на диск, если они не влезают в память. Это замедлит запрос, но предотвратит падение.
SET SESSION join_spill_enabled = true; SET SESSION aggregate_spill_enabled = true;
Типичные ошибки
- Путаница в параметрах:
query.max-memory-per-nodevsquery.max-total-memory-per-node. Первый — на одну задачу, второй — на все задачи запроса на узле. - Игнорирование memory pools: Попытка установить
query.max-memory-per-nodeравным всему доступному объёму General Pool. Если запустится два таких запроса параллельно на одном узле — оба упадут. - Глобальный лимит без учёта воркеров:
query.max-memory=1TBпри трёх воркерах — бессмысленная настройка, так как реальное ограничение произойдёт на уровне узла.
Итог: Настройка памяти в Trino — это поиск баланса между параллельным выполнением запросов и «разрешением» тяжёлым операциям завершиться. Начинайте с консервативных лимитов, мониторьте пиковое потребление и увеличивайте лимиты точечно, понимая свой workload.