Производительность

Почему так долго работает запрос с JOIN?

Все основные виды JOIN (кроме SEMI/ONLY) строят декартово произведение строк по совпадающим значениям ключей. Если одно или несколько значений ключа являются особенными, то по нему может оказаться большое число строк с каждой из сторон и, как следствие, гигантское декартово произведение. В большинстве случаев JOIN работает медленно именно из-за этого, а такие особые значения с прикладной точки зрения оказываются не так интересны, и достаточно их исключить в WHERE.

Найти аномально часто встречающиеся значения можно с помощью двух запросов по каждой из таблиц с GROUP BY по тем же ключам, что и у JOIN, используя агрегатную функцию COUNT и убывающую сортировку по её результату. Ещё можно добавить LIMIT 100, чтобы отработало ещё быстрее.

Также бывают запросы, где на самом деле декартово произведение в JOIN не нужно, т. к. потом всё равно от значений тем или иным способом оставляют только уникальные значения. В этом случае стоит убрать дубли в подзапросе через GROUP BY или SELECT DISTINCT ещё до JOIN.

Мой запрос раньше работал за N часов, а теперь стал работать за M часов. В чем причина?

При анализе производительности стоит обратить внимание на следующие моменты:

Поменялся ли входной объём данных

Например, в запрос стало попадать больше таблиц или существенно изменился их объём. Чтобы проверить, поменялся ли входной объём данных, можно кликнуть в плане запроса на первую YTsaurus операцию и посмотреть в интерфейсе YTsaurus на Data flow/Data weight:

Поменяли ли план исполнения запроса

Если план изменился, то это могло произойти по нескольким причинам:

  • Стала применяться другая стратегия JOIN. Про критерии срабатывания той или иной стратегии смотри отдельную статью.
  • Изменилась схема некоторых входных таблиц в папке. YQL выравнивает схему входных таблиц, что приводит к появлению дополнительных операций.
  • Поменялись оптимизаторы YQL с релизом новой версии.

Используется ли в запросе пул с выделенными гарантиями

В запросе должна быть указана прагма yt.StaticPool для применения пула. Если её нет, то время исполнения запроса никак не гарантируется. На него могут повлиять как соседние запросы, так и сезонные колебания нагрузки на кластера (перед ревью нагрузка на кластера, как правило, возрастает). Также стоит убедиться, что пул задаётся прагмой yt.StaticPool, а не yt.Pool. Последняя является динамической прагмой, применяемой уже в рантайме. Из-за этого служебные операции, запущенные на этапе оптимизации, не используют её значение и запускаются в пуле по умолчанию, что может приводить к их существенному замедлению. Прагма yt.StaticPool применяется для всех операций, в том числе и служебных.

Проверка суммарных статистик запроса

На вкладке Statistics YQL-запроса можно посмотреть его суммарные статистики:

Здесь в первую очередь нужно обратить внимание и сравнить значения следующих статистик:

  • yt/total/Job_CalcTime (Sum) — полезное время, проведенное во всех джобах с пользовательским кодом всех YTsaurus-операций запроса. Изменение этого значения при отсутствии изменения входного объёма данных говорит об изменениях производительности в YQL-рантайме.
  • yt/total/Job_InputDecodeTime (Sum) — время, потраченное YQL декодером на чтение входных данных во всех джобах всех YTsaurus-операций запроса. Изменение этого значения при отсутствии изменения входного объёма данных говорит об изменениях производительности в YQL декодере.
  • yt/total/Job_OutputEncodeTime (Sum) — время, потраченное YQL энкодером на запись данных во всех джобах всех YTsaurus-операций запроса. Изменение этого значения при отсутствии изменения входного объёма данных говорит об изменениях производительности в YQL энкодере.
  • yt/total/CodeGen_FullTime (Sum) — время, потраченное на кодогенерацию во всех джобах всех YTsaurus-операций запроса. Изменение этого значения при том же числе YTsaurus-джобов говорит об изменениях производительности кодогенерации YQL-рантайма. Если это время составляет существенную долю от Job_CalcTime, то стоит рассмотреть вариант отключения LLVM в запросе через pragma config.flags("LLVM", "OFF").
  • yt/total/Join_Spill_Count (Sum) — ненулевое значение этой статистики говорит о проблемах производительности JOIN в запросе. Если ключи по первой стороне джойна имеют очень много записей, то они не помещаются в памяти и сохраняются на диск. Это приводит к существенной деградации времени YTsaurus-джобов, выполняющих JOIN. Для решения этой проблемы можно увеличить значение прагмы yt.CommonJoinCoreLimit, либо поменять местами стороны JOIN.
  • yt/total/data/input/data_weight (Sum) — суммарный объём данных, обработанный всеми джобами всех YTsaurus-операций запроса. При отсутствии изменений в плане запроса, изменение этой статистики говорит об изменении входного объёма данных запроса.

Поиск самой медленной YTsaurus-операции запроса

На вкладке Progress YQL-запроса нужно переключиться в режим Timeline:

В этом режиме наглядно видно, какая YTsaurus-операция больше всего повлияла на время исполнения YQL-запроса. Можно также раскрыть детали исполнения разных стадий операции:

Здесь видно, что операция долго стояла в статусе Pending, т. е. ей не хватало лимитов на число работающих операций в пуле.

Для дальнейшего анализа можно открыть медленную операцию в YTsaurus интерфейсе, кликнув на её название в левой колонке timeline. В YTsaurus-операции в первую очередь стоит обратить внимание на Total job wall time и Total CPU time spent. Если эти значения для двух запусков YQL-запроса существенно не изменились, но при этом изменился Duration, то это сигнализирует о проблемах в пуле запроса. Нужно кликнуть на название пула

и открыть его в интерфейсе YTsaurus на вкладке Monitoring для анализа его нагрузки.