Guides
Trino

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). Частые причины:

  1. Неравномерное распределение данных (data skew): Один ключ джоина содержит 10 миллионов строк, а остальные — по сотне. Задача, обрабатывающая этот «тяжёлый» ключ, требует гигантской хэш-таблицы и падает.
  2. Запрос без ограничений: SELECT * FROM huge_table ORDER BY column пытается отсортировать всю таблицу сразу в памяти.
  3. Некорректные настройки: Лимиты query.max-memory-per-node установлены без учёта доступной памяти JVM и размера General Pool.

Практическая настройка под реальную нагрузку

  1. Рассчитайте базовые лимиты. На 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 узлов, но часто его делают меньше, чтобы оставить запас для параллельного выполнения других запросов.

  2. Мониторьте и анализируйте. Используйте веб-интерфейс Trino (/ui/query.html). Смотрите на пиковое распределение памяти (Peak User Memory) у упавших и медленных запросов. Это ваш главный источник истины.

  3. Боритесь с data skew. Используйте JOIN на распределённых ключах с равномерной дистрибуцией. Иногда помогает увеличение query.max-memory-per-node как временное решение, но это лишь маскировка проблемы.

  4. Настройте spooling на диск. Для операций сортировки и джоинов можно разрешить сброс промежуточных данных на диск, если они не влезают в память. Это замедлит запрос, но предотвратит падение.

    SET SESSION join_spill_enabled = true;
    SET SESSION aggregate_spill_enabled = true;
    

Типичные ошибки

  • Путаница в параметрах: query.max-memory-per-node vs query.max-total-memory-per-node. Первый — на одну задачу, второй — на все задачи запроса на узле.
  • Игнорирование memory pools: Попытка установить query.max-memory-per-node равным всему доступному объёму General Pool. Если запустится два таких запроса параллельно на одном узле — оба упадут.
  • Глобальный лимит без учёта воркеров: query.max-memory=1TB при трёх воркерах — бессмысленная настройка, так как реальное ограничение произойдёт на уровне узла.

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