В аналитике больших данных системы массивных параллельных вычислений часто находятся под постоянной нагрузкой в режиме 24/7. Из десятков и сотен тысяч запросов в день многие исполняются одновременно и конкурируют за ограниченные ресурсы вычислительного кластера. Чем рациональнее каждый отдельный запрос их использует, тем больше запросов система сможет обслуживать параллельно. Соответственно, выше пропускная способность за конкретный отрезок времени. Как правило, проблема нехватки ресурсов остро ощущается в пиковые часы нагрузки. Можно бесконечно до совершенства настраивать и править параметры сессии на каждый запрос индивидуально вручную, но нам — команде разработки платформы данных Data Ocean Nova — всегда хочется иметь более системный подход.
В сегодняшней публикации мы расскажем о том, как реализовали идею автоматической системы предсказания потребления ресурсов SQL-запросами для Impala и StarRocks, основанную на ML-принципах, и сделали её частью платформы данных.

Мотивация
Compute-движки стараются предугадать, сколько памяти потребуется запросу, и резервируют часть ресурсов заранее. Идеально было бы выделить ровно столько, сколько нужно, но это число сильно колеблется от наполнения таблиц, настроек системы, решений планировщика и даже числа вычислительных узлов в кластере. Зачастую запросам выделяется куда больше памяти, чем им реально понадобится, — как страховка от непредвиденных обстоятельств, но при таком подходе резерв всегда простаивает, пока задача не завершится. Ресурсов на другие SQL-запросы в пиковые нагрузки начинает не хватать, появляются очереди. Для систем уровня business critical несоблюдение SLA может повлечь репутационные и финансовые издержки.
max_peak — максимальное потребление памяти на одном узле за время работы запроса. Использование памяти в моменте будет меняться по ходу выполнения запроса и отличаться на разных узлах из-за разделения таблиц по ним. Однако одновременно больше этого числа ни одному узлу не понадобится.
Зачем же тогда оставляют большой запас? Дело в том, что выделить меньше max_peak может оказаться ещё хуже. Из-за нехватки памяти запрос начнёт выгружать промежуточные временные данные на дисковую подсистему. Spill-операции могут кратно замедлить выполнение. Также запрос рискует упасть с ошибкой OOM, и придётся запускать его в потоке заново, а время работы пропало зря. Не может же у такой важной и повсеместной проблемы не быть каких-то решений?
Испытанные практики
Действительно, давно существует несколько распространённых подходов. Рассмотрим их по порядку и проанализируем применимость:
-
Расширение кластера. Очевидный, но самый затратный вариант: добавляем узлы, платим за лицензии и обслуживание в надежде, что запаса ресурсов хватит на все запросы. Однако причина проблемы — неверная оценка потребления памяти — при этом никуда не уходит. Завышенные оценки масштабируются вместе с кластером и способны поглотить даже кратно увеличенные ресурсы, возвращая очереди и деградацию при следующем всплеске нагрузки. В результате инфраструктура дорожает, затраты на лицензионное обеспечение и поддержку как производные от метрик оборудования также возрастают, а ожидаемого эффекта для пользователей не наступает. Получается, что это не решение причин, а лишь временная мера. К тому же с учётом нынешних реалий (третий квартал 2026 г.) стоимость оборудования на рынке и сроки его поставки вызывают устойчивое желание выжать всё возможное из имеющихся мощностей.
-
Настройка кластера или конкретного движка с помощью доступных механизмов управления ресурсами (Admission Control). Попытка изолировать профили нагрузки: кластер делится на пулы под разные типы запросов или нагрузки, чтобы тяжёлые ETL не мешали интерактивной аналитике. На практике возникает дилемма — нагрузки действительно разводятся по пулам и замедление всей работы становится менее вероятным, но в каждом отдельном пуле доступных ресурсов становится меньше, а значит, проблема неверной оценки проявляется острее и чаще. Похожая ситуация происходит и при распределении запросов по группам/пулам исполнителей (такой функционал поддерживается StarRocks и Impala). Завышенные оценки быстрее упираются уже в лимит каждого пула, очереди и таймауты растут, а тонкая настройка пулов превращается в постоянную ручную работу. В результате общая доля утилизации ресурсов на кластере может даже снизиться.
-
Ручная оценка запросов. Пусть составители сами решают, сколько их запрос должен потреблять! Но проблема в том, что с течением времени реальный max_peak будет меняться вместе с наполнением данными таблиц, с возрастанием инкрементов обработки или в случае непрогнозируемых перерасчётов. Ручная переоценка будет требоваться вновь и вновь. Тестовые выполнения, дополнительная отладка всех новых запросов и периодическая — старых потребуют ещё больше времени и усилий, чем настройка кластера. Хуже, если объём таблиц зависит от даты, например меняется от дня недели или месяца (закрытие операционного дня или начисление процентов по счетам, пролонгация договоров, закрытие отчётного периода с массовыми корректировками учётной системы). Тогда ручной лимит придётся выставлять сразу с большим запасом из расчёта на самый нагруженный день или вводить систему метрик размера изменений. Но всё равно возвращается изначальная проблема чрезмерного резервирования.
-
Cost-Based Optimization — встроенная оценка стоимости запроса оптимизатором движка. Например, в StarRocks и Impala существует параметр estimate — прогноз max_peak запроса, основанный на плане его выполнения. Почему бы просто не взять его: движок точно знает свои потребности, нужно лишь вовремя и регулярно собирать статистику. Однако на практике эта оценка часто и сильно завышена, порой до максимальных размеров всего кластера. Причина понятна — отсутствие точных статистических гистограмм распределения значений в полях таблиц, на основе которых оптимизатор может произвести точный расчёт. Теоретически это самый удобный и рабочий из методов: автоматическое ограничение на основе внутренних параметров плана запроса. Нужно лишь доработать точность самих предсказаний. Именно над этой задачей мы и решили поработать.
Научный подход
Здесь на помощь приходит машинное обучение. Предсказание max_peak — классическая задача регрессии. Она не решается простым коэффициентом на estimate, так как движок ошибается с разной степенью и не всегда даже в большую сторону. Множество примеров в виде отработавших запросов также идеально подходит для использования ML. Нужно лишь выделить важные признаки из плана выполнения запроса, а дальше модель найдёт закономерности и связи между ними и получит формулу для предсказания.
Модель работает с числами, и сам текст запроса мало что ей скажет, поэтому воспользуемся Explain как своеобразным рентгеном. Выделим из него набор признаков, с которыми уже будем работать. Один из важнейших — оценка самого движка, так как она уже опосредованно включает в себя множество факторов. К ней добавим количество операций сканирования, минимальный, средний и максимальный размеры участвующих таблиц, параметры таблиц наибольшего Join’а и другие. Для теста взяли данные из разных источников и убрали точно повторяющиеся наборы значений.
Также к предсказаниям необходимо добавлять небольшой запас. Модель старается наиболее точно оценить число, и её предположения будут разбросаны вокруг реального max_peak симметрично. Нам намного предпочтительнее оказаться несколько выше реального значения и выделить чуть больше ресурсов, чем допустить уход запроса в сброс на диск и замедление. Изначально для простоты использовалась средняя абсолютная ошибка. Однако сдвиг всех предсказаний на одно число влияет на запросы по разному из-за относительного размера, поэтому позже перешли на динамическую добавку.
«Идеальные» предсказания — такие, где одновременно улучшаются и ручная оценка, и оценка движка, при этом оставаясь выше max_peak. Также считаются случаи, где оценка уходила в сброс на диск, а предсказание нет. Этот параметр помогает отслеживать качество работы модели на всей выборке в совокупности, независимо от размера запросов.
Трудности разработки
Подход оказался вполне рабочим. Хотя и без технических вызовов не обошлось.
Основная проблема заключается в дисбалансе данных. На обучающей выборке и в целом в промышленном контуре доминируют маленькие запросы. Несмотря на превосходящее количество, они оказывают меньшее влияние на кластер относительно более редких больших. При этом их набор важных признаков отличается, и дополнительный запас к предсказанию на них значительно ощутимее. В итоге самые маленькие запросы были исключены. Пороговое значение определили опытным путём. Чем оно ниже — тем более заметно искажение для других предсказаний, чем выше — тем больше обучающего материала теряется. Отметка 0.5 GiB оказалась хорошим компромиссом. По похожей причине удалили и единичные выбросы аномальных запросов, чтобы модель не завышала оценку остальным.
Но простого отсекания мало — сама выборка распределена неравномерно. Менее тяжёлые запросы встречаются чаще, а предсказывать все нужно одинаково хорошо. Ответом стала стратификация — разбиение на корзины для выравнивания разных групп запросов. На обучение уходит примерно одинаково запросов разных диапазонов потребления, и модель предсказывает без предпочтения определённых. Распределение считается по самым свежим запросам из выборки. Это помогает реагировать на изменения в наполнении таблиц или профиле нагрузки.
Выбор лучшей модели тоже оказался не такой простой задачей. Хорошие кандидаты имеют очень похожий стандартный R2-скор. Более того, модель с лучшим скором может показывать итоговый результат хуже за счёт разного времени выполнения запросов. Для нашего случая этот подход оказался непоказательным, особенно после добавления запаса к предсказанию. Вместо него были созданы собственные метрики. Статистика каждой модели сохраняется и обновляется при изменении проверочной выборки для сравнения между собой.
Одна из основных наших метрик — это отклонение от max_peak, умноженное на время работы запроса. Экономия памяти с учётом времени приближённо показывает эффект от использования модели на кластере. Помимо неё используются «идеальные» предсказания, сброс на диск, средняя абсолютная ошибка и другие параметры, в том числе отдельные для корзин.
Проверяем прототип на практике
Пришло время представить и протестировать пилотную версию. Эксперименты проводились на ~4 000 запросов из реальных продуктовых сценариев в изолированном контуре. Для лучшего приближения использовали 125 таблиц с объёмами и распределением, сравнимыми с производственными. Запросы выполнялись последовательно и были разделены по трём группам: < 3 GiB, 3–10 GiB и > 10 GiB.
Оценивались изменения потребления ресурсов, времени выполнения, сброс на диск и отсутствие ошибок. Модель хорошо себя показала: в 67% случаев предсказания улучшили и оценку движка, и выставленные вручную ограничения, при этом оставаясь выше max_peak. На лёгких запросах, ввиду их количества, был получен наибольший выигрыш по памяти в 59,64%. На средних запросах — по общему времени работы в 48,40%. Так как тяжёлые запросы чаще переходят в выгрузку временных файлов, среди них этот показатель изменился больше всего — на 23,48%. Вместе с тем 5 запросов упали с OOM-ошибкой.
Ряд ошибок основывался на неверно предполагаемом размере таблиц, участвующих в запросе. Например, селективность: некоторые предикаты после применения оставляют значительно большую часть данных, чем ожидает движок, и памяти выделяется недостаточно. Во многих SQL-движках эта проблема решается с помощью гистограмм: оптимизатор знает, как именно данные распределены внутри диапазона, и может более точно оценить селективность предиката. Однако ни Impala, ни StarRocks гистограмм не поддерживают, а оперируют агрегированной статистикой (число строк, число уникальных значений), которая не отражает реальных перекосов в распределении.
Для компенсации этого ограничения был разработан словарь предикатов. В ходе обработки запросов фиксируются расхождения между оценкой оптимизатора и фактическим объёмом данных, прошедших через предикат. Для каждой пары «таблица — предикат» сохраняются коэффициенты наблюдаемых отклонений; если пара встречается несколько раз — берётся наибольший коэффициент, следуя принципу консервативной оценки: лучше немного завысить объём данных, чем недооценить. Когда известная пара появляется в плане нового запроса, модель получает поправочный коэффициент и учитывает его при оценке.
Ограничения подхода
Эксперименты подтвердили эффективность решения, однако показали и ограничения, вызванные самим использованием ML-подхода. От них невозможно полностью избавиться, поэтому важно их понимать и учитывать.
-
Холодный старт. Точность модели увеличивается с объёмом имеющихся данных для обучения. При первых запусках предсказания будут неточными, пока не соберётся достаточно истории запросов.
-
Зависимость от актуальной статистики. Предсказания опираются на признаки из Explain запросов. Если статистика устарела и не соответствует действительности, модель получит некорректные входные данные. Дополнительные признаки, количество запросов и решения вроде словаря предикатов помогают сгладить эту проблему, но своевременный сбор статистики таблиц всегда будет эффективнее.
-
Дрейф данных. Модель учится предсказывать конкретные запросы за окно времени. Со временем таблицы будут расти, появятся новые виды запросов или профиль нагрузки на кластер изменится. Таким образом, модель подвержена устареванию и требует переобучения через определённое время.
Платформенный сервис — жизнь модели в production
Постоянно переобучать модели муторно, поэтому мы отдали эту работу платформенному сервису Data Ocean Nova. Сейчас созданы автоматизированные процессы для сбора новых запросов, обработки обучающих данных, тренировки моделей, ведения статистики и очистки хранилища. Сервис qModel может выполнять задачу достаточно автономно.
Помимо автоматизации, мы пользуемся и другими преимуществами глубокой интеграции с Data Ocean Nova. Сервис является Kubernetes-приложением и общается напрямую с другими внутренними сервисами. Может работать с хранилищем S3, брать запросы из общей подсистемы журналирования, выставлять метрики в Prometheus. Доступен по вызовам через endpoint’ы и по кнопке прямо в тонком пользовательском клиенте.
Появилось и множество продуктовых дополнений: поддержка TLS-соединения, доступ через LDAP/Keycloak (OAuth2 JWT). Мультитенантность для нескольких экземпляров Impala/StarRocks, проверка необходимых ролей (RBAC), имперсонация пользователя. Модель можно заменить на лучшую через API без рестарта, и сервис предоставляет свои метрики работы.
Обратиться к модели также можно через API, передав в endpoint лишь текст SQL-запроса. Сервис qModel сам получит у Impala/StarRocks план запроса, достанет нужные признаки и передаст текущей выбранной модели. В ответе вернётся её предсказание максимального потребления и рекомендуемая добавка к нему.
Применение модели на практике
После всех доработок, модель проверили на большом объёме реальных данных. Обучили на ~80 000 продуктовых запросов за несколько недель и посмотрели её предсказания на следующей неделе.
Из графика видно, что весь разброс предсказаний модели практически полностью лежит под скользящим средним оценок движка. А среднее самих предсказаний почти скрывается за max_peak на таком приближении.
Снова оставим только запросы с max_peak выше 1 GiB и исключим любые выбросы с 10x и более ошибкой. Также добавим среднюю абсолютную ошибку (в данном случае 155 MiB) ко всем предсказаниям модели для смещения разброса выше max_peak. Так как мы убрали наиболее плохие оценки движка из рассмотрения, на получившейся выборке он сможет показать себя с лучшей стороны. Предсказания разделим на реальное потребление, чтобы видеть отклонения в процентах.
Идеальное предсказание должно быть как можно ближе к единице сверху. Нетрудно заметить, что даже на такой выборке модель значительно превосходит оценку движка. Большее отклонение в начале обусловлено большим относительным размером добавки для маленьких запросов.
Для лучшего представления, как применение модели отражается на работе кластера, стоит учитывать и время выполнения запросов. Рассмотрим отдельно отклонения предсказаний ниже и выше реального потребления, помноженные на время работы. Также разобьём выборку на равные корзины в зависимости от оценки движка.

Хотя модель и ошибается вниз чаще на самых больших запросах, она в разы сокращает переоценку движка. Так как последняя в среднем существенно выше, в сумме модель уменьшает общее отклонение от max_peak в 8,54 раза, или на 2036 GiB\*h в данном примере. Если же рассматривать вместе с выбросами, не уходящими в PiB, то на 3168 GiB\*h.
Последний график показывает процент «идеальных» предсказаний, улучшающих одновременно обе оценки. В сумме модель добивается улучшения на 85,8% запросов. Оценка движка получает меньше 100% (71,5% в данном примере) из-за местами более хороших ручных лимитов и ухода ниже max_peak.
А как дела у других движков?
Полученный большой практический опыт эксплуатации подхода и самого сервиса на Impala перенесён нами и в StarRocks. StarRocks демонстрирует стремительное развитие и, что ожидаемо, копирует лучшие практики по управлению конкурентной нагрузкой у Impala, являясь, по сути, наряду с Doris, её идеологическим ответвлением. Порой копируются не только принципы работы, но и отдельные конфигурационные параметры «слово в слово». Несмотря на более зрелый подход с точки зрения Cost-Based Optimization, проблема предсказания потребления ресурсов запросами для него на практике оказалась даже более актуальной.
План выполнения запроса StarRocks разделён на несколько логических частей со схожим набором параметров и метрик, хоть и в чуть меньшем количестве, чем у Impala. На данный момент весь функционал сервиса qModel полностью адаптирован для работы со StarRocks.
Нами рассматривалась и работа с Trino — к сожалению, на данный момент он оказался технически неподготовленным для такого подхода. Большая разница в практиках и значительно более скудные метрики заставили самостоятельно сопоставлять дополнительную информацию из статистики. К примеру, план запроса не предоставляет среднюю длину строки, и она приближённо оценивалась по типам данных каждого столбца. Такие адаптации увеличивают погрешность предсказаний. Основной проблемой оказалась невозможность ограничить потребление памяти в Trino без прерывания выполнения самого запроса.
Что дальше
В целом задачу можно считать решённой, но ведь всегда можно сделать лучше. Так и тут есть несколько направлений для развития.
-
Классификация типов запросов. Сейчас стратификация происходит по оценке самого движка, но разделять запросы можно по множеству признаков. Хорошее разбиение на чёткие классы запросов позволит точнее вычислить закономерности внутри них.
-
Ансамбли моделей. Использование разных моделей на разных частях выборки (например, классах из предыдущего пункта) может создать определённую специализацию и увеличить точность предсказаний. Похожим образом объединение результатов работы моделей разных архитектур даст больше информации о запросе. Они даже могут получать свои собственные наборы признаков для фокусировки внимания.
-
Рекомендации к структуре запросов. Модель уже анализирует планы выполнения запросов, и есть опыт определения стандартных ошибок. Превращение этого в рекомендации к написанию более эффективных запросов также поможет сократить нагрузку на кластер.
Спасибо, что дочитали до конца! Подписывайтесь на блог Data Sapience на Хабре и наш телеграм-канал.
ссылка на оригинал статьи https://habr.com/ru/articles/1061866/