JVM-тюнинг: heap, GC, headroom для воркера и координатора
Trino написан на Java и исполняется как JVM-процесс. Это значит, что производительность и стабильность кластера напрямую зависят от настроек JVM: версии Java, размера heap, выбора garbage collector. Неверно настроенная JVM — это либо OutOfMemoryError, кладущий воркеры, либо длинные GC-паузы, в которые кластер замирает.
Этот урок — про тюнинг JVM под Trino: какую именно версию Java требует сервер, как задавать heap, какой GC использовать и чем настройки координатора отличаются от настроек воркера.
Версия Java: сервер требует ровно Java 24
Начнём с жёсткого факта, который нельзя угадывать или приближать.
Сервер Trino требует ровно Java 24. Не «Java 24 или новее», не «любая свежая LTS» — Trino пинит одну конкретную мажорную версию Java для сервера. Релиз 481, который мы изучаем, требует именно Java 24. Запустить сервер Trino на Java 23 или на Java 25 не получится — пин на одну мажорную версию означает, что и более старая, и более новая Java сервером не поддерживаются.
JDBC-драйвер Trino — это отдельная история. Он требует Java 11 или новее: драйвер встраивается в чужие JVM-приложения, и для него поддерживается широкий диапазон, а не одна версия.
| Компонент | Требование к Java |
|---|---|
| Сервер Trino (координатор и воркеры) | ровно Java 24 — одна пинованная мажорная версия |
| JDBC-драйвер Trino | Java 11 или новее |
Не полагайся на память при выборе версии Java для сервера. Trino пинит конкретную мажорную версию, и она может смениться в любом релизе. Перед развёртыванием конкретной версии Trino ОБЯЗАТЕЛЬНО сверь требование к Java со страницей deployment именно того релиза, что разворачиваешь. Для релиза 481 это ровно Java 24. Поставить не ту мажорную версию — сервер не стартует.
Почему сервер пинит одну версию, а драйвер — нет? Сервер — это приложение, которое команда Trino полностью контролирует: она тестирует его против одной конкретной JVM и гарантирует поведение именно на ней, используя в том числе свежие возможности этой версии. Драйвер же живёт в чужих приложениях с разными требованиями к Java — для него поддерживают широкий диапазон.
Размер heap: свойство -Xmx
Главная цифра JVM-тюнинга — размер heap, задаётся в etc/jvm.config. Есть два способа.
Абсолютный размер через -Xmx:
-Xmx64G
Доля от доступной памяти контейнера/машины через проценты:
-XX:InitialRAMPercentage=80
-XX:MaxRAMPercentage=80
MaxRAMPercentage удобен в контейнерах: JVM сама вычисляет heap как процент от памяти, выделенной контейнеру. Один и тот же jvm.config корректно работает на подах разного размера — это особенно полезно на Kubernetes. InitialRAMPercentage, равный MaxRAMPercentage, сразу выделяет heap целиком, без постепенного роста.
Сколько heap давать? Для продакшен-воркеров — обычно десятки гигабайт и больше: 32GB как нижняя планка серьёзной нагрузки, часто существенно больше. Чем больше heap, тем больше памяти доступно под user memory запросов.
Но больше — не всегда лучше. Heap нельзя задавать «под завязку» доступной памяти:
JVM-процессу нужна память сверх heap: метаданные классов, стеки потоков, прямые буферы. И операционной системе нужен запас. Если -Xmx равен всей RAM ноды, off-heap-память JVM и ОС окажутся в дефиците — нода уйдёт в своп или будет убита OOM-killer ядра. Поэтому heap — это большая, но не вся доступная память.
Heap headroom: связь с моделью памяти
Вспомним модуль про память. Внутри heap Trino резервирует heap headroom — свойство memory.heap-headroom-per-node, по умолчанию 30% максимального heap — под system-аллокации, которые движок не контролирует резервированиями (exchange-буферы, парсинг, кэши, неточность учёта).
Здесь JVM-тюнинг и модель памяти стыкуются. Trino на старте проверяет правило:
query.max-memory-per-node + memory.heap-headroom-per-node < -Xmx
То есть -Xmx из jvm.config и memory-свойства из config.properties связаны и должны быть согласованы. Задав -Xmx, ты задаёшь и базу, от которой считаются дефолты query.max-memory-per-node (30% от -Xmx) и memory.heap-headroom-per-node (30% от -Xmx). При дефолтах правило выполняется автоматически: 30% + 30% = 60% < 100%. Но если эти свойства переопределять вручную, согласованность придётся держать самому — иначе Trino откажется стартовать.
JVM-тюнинг нельзя делать в отрыве от memory-свойств. -Xmx живёт в jvm.config, а query.max-memory-per-node и memory.heap-headroom-per-node — в config.properties, но это единая система. Меняя -Xmx, держи в голове правило per-node + headroom < -Xmx. Рассогласование этих файлов — частая причина того, что нода не стартует после «безобидного» изменения heap.
Garbage collector
JVM управляет heap через garbage collector — он освобождает память от объектов, на которые больше нет ссылок. Выбор GC и его настройка задаются в etc/jvm.config.
Для Trino важно одно свойство GC — длина пауз. Когда GC выполняет stop-the-world паузу, JVM-процесс замирает целиком: воркер в этот момент не обрабатывает данные. Короткие частые паузы терпимы; длинные паузы в секунды означают, что воркер на эти секунды выпал из работы — растёт латентность запросов, в плохом случае нода выглядит «зависшей».
Поэтому под Trino используют современный low-pause garbage collector — сборщик, спроектированный держать паузы короткими даже на больших heap. Это критично, потому что Trino-воркеры работают с десятками гигабайт heap, а наивный GC на таком объёме давал бы паузы в секунды.
Конкретный рекомендуемый GC и его флаги между версиями Trino могут меняться — сверяй их со страницей deployment той версии, что разворачиваешь. Принцип же неизменен: под Trino нужен GC, держащий паузы короткими на больших heap, потому что каждая длинная пауза — это секунды, на которые воркер выпал из работы.
Координатор и воркер: разные настройки
Координатор и воркер исполняют разную работу — значит, и JVM-настройки у них разные.
Воркер обрабатывает данные: хэш-таблицы join, буферы агрегаций, сортировка. Ему нужен большой heap под user memory запросов — это рабочая лошадка, и от его heap зависит, какие запросы кластер потянет.
Координатор данные не обрабатывает (при node-scheduler.include-coordinator=false). Его работа — парсинг, планирование, управление воркерами, обслуживание клиентского протокола и Web UI. Память координатора уходит на план запросов, метаданные, состояние активных запросов. Профиль нагрузки иной, и heap координатора подбирают под него, а не копируют с воркера.
| Аспект | Координатор | Воркер |
|---|---|---|
| Основная работа | парсинг, планирование, управление | обработка данных |
| На что идёт heap | планы, метаданные, состояние запросов | user memory: join, агрегации, сортировка |
| Размер heap | под координацию | большой, под нагрузку запросов |
| GC | low-pause | low-pause |
| Версия Java | ровно Java 24 | ровно Java 24 |
Версия Java и тип GC у них общие — это свойства платформы. А вот размер heap осмысленно различать: воркеру — щедро под обработку данных, координатору — под его собственный профиль координации.
Попробуй сам
Поэкспериментируй с JVM-настройками Trino на тестовом кластере.
- Проверь версию Java, на которой реально работает твой Trino: подключись по
jmx-коннектору и найди MBean с информацией о рантайме, либо посмотри стартовый лог. Сверь с требованием релиза — для 481 это должна быть Java 24. - В
etc/jvm.configворкера поменяй-Xmxна заведомо маленькое значение (например,-Xmx1G), перезапусти и запусти тяжёлый join поtpch.sf100. Поймай поведение при нехватке heap. Затем верни вменяемый-Xmx. - Сделай намеренно несогласованную конфигурацию: задай
query.max-memory-per-nodeиmemory.heap-headroom-per-nodeтак, чтобы их сумма превысила-Xmx. Перезапусти и убедись, что Trino отказывается стартовать — прочитай текст ошибки про нарушенное правило. - Через
jmx-коннектор посмотри метрики GC — частоту и длительность пауз сборки мусора при нагрузке.
Цель — закрепить, что версия Java для сервера пинована и сверяется по документации, а -Xmx и memory-свойства образуют единую согласованную систему.