Перейти к основному содержимому
Версия: 2.0.x

План запроса

Оптимизация производительности запросов — распространенная задача в аналитических системах. Медленные запросы могут ухудшить пользовательский опыт и общую производительность cluster. В Selena понимание и интерпретация планов запросов и профилей запросов является основой для диагностики и улучшения медленных запросов. Эти инструменты помогают вам:

  • Определить узкие места и дорогостоящие операции
  • Обнаружить неоптимальные стратегии соединения или отсутствующие индексы
  • Понять, как данные фильтруются, агрегируются и перемещаются
  • Устранять неполадки и оптимизировать использование ресурсов

План запроса — это подробная дорожная карта, созданная Selena FE, которая описывает, как будет выполнен ваш SQL-оператор. Он разбивает запрос на серию операций — таких как сканирование, соединение, агрегация и сортировка — и определяет наиболее эффективный способ их выполнения.

Selena предоставляет несколько способов просмотра плана запроса:

  1. Оператор EXPLAIN: Используйте EXPLAIN для отображения логического или физического плана выполнения запроса. Вы можете добавить опции для управления выводом:

    • EXPLAIN LOGICAL <query>: Показывает упрощенный план.
    • EXPLAIN <query>: Показывает базовый физический план
    • EXPLAIN VERBOSE <query>: Показывает физический план с подробной информацией.
    • EXPLAIN COSTS <query>: Включает оценочные затраты для каждой операции, что используется для диагностики проблем со статистикой
  2. EXPLAIN ANALYZE: Используйте EXPLAIN ANALYZE <query> для выполнения запроса и отображения фактического плана выполнения вместе с реальной статистикой времени выполнения. См. документацию по Explain Anlayze для подробностей.

    Пример:

    EXPLAIN ANALYZE SELECT * FROM sales_orders WHERE amount > 1000;
  3. Query Profile: После выполнения запроса вы можете просмотреть его подробный профиль выполнения, который включает время, использование ресурсов и статистику на уровне операторов. См. документацию по Query Profile для получения информации о том, как получить доступ и интерпретировать эту информацию.

    • SQL-команды: SHOW PROFILELIST и ANALYZE PROFILE FOR <query_id>: можно использовать для получения профиля выполнения для конкретного запроса.
    • HTTP-сервис FE: Доступ к профилям запросов через веб-интерфейс Selena FE, перейдя в раздел Query или Profile, где вы можете искать и просматривать детали выполнения запросов.
    • Управляемая версия: В облачных или управляемых развертываниях используйте предоставленную веб-консоль или панель мониторинга для просмотра планов запросов и профилей, часто с расширенными возможностями визуализации и фильтрации.

Обычно план запроса используется для диагностики проблем, связанных с тем, как запрос планируется и оптимизируется, в то время как профиль запроса помогает выявить проблемы производительности во время выполнения запроса. В следующих разделах мы рассмотрим ключевые концепции выполнения запросов и пройдем через конкретный пример анализа плана запроса.

Поток выполнения запроса

Жизненный цикл запроса в Selena состоит из трех основных фаз:

  1. Планирование: Запрос проходит через парсинг, анализ и оптимизацию, завершаясь генерацией плана запроса.
  2. Планирование выполнения: Планировщик и координатор распределяют план по всем участвующим backend-узлам.
  3. Выполнение: План выполняется с использованием pipeline-движка выполнения.

SQL Execution Flow

Структура плана

План Selena имеет иерархическую структуру:

  • Fragment: Срез работы верхнего уровня; каждый fragment порождает множество FragmentInstances, которые выполняются на разных backend-узлах.
  • Pipeline: Внутри instance pipeline связывает операторы вместе; несколько PipelineDrivers выполняют один и тот же pipeline параллельно на отдельных ядрах CPU.
  • Operator: Атомарный шаг — сканирование, соединение, агрегация — который фактически обрабатывает данные.

profile-3

Pipeline-движок выполнения

Pipeline Engine выполняет план запроса параллельно и эффективно, обрабатывая сложные планы и большие объемы данных для высокой производительности и масштабируемости.

pipeline_opeartors

Стратегия объединения метрик

По умолчанию Selena объединяет уровни FragmentInstance и PipelineDriver для уменьшения объема профиля, в результате чего получается упрощенная трехуровневая структура:

  • Fragment
  • Pipeline
  • Operator

Вы можете управлять этим поведением объединения через переменную сессии pipeline_profile_level.

Пример

Как читать план запроса и профиль

  1. Понимание структуры: Планы запросов разделены на фрагменты, каждый из которых представляет этап выполнения. Читайте снизу вверх: сначала узлы сканирования, затем соединения, агрегации и, наконец, результат.

  2. Общий анализ:

    • Проверьте общее время выполнения, использование памяти и соотношение CPU/wall time.
    • Найдите медленные операторы, отсортировав по времени оператора.
    • Убедитесь, что фильтры проталкиваются вниз, где это возможно.
    • Ищите перекос данных (неравномерное время операторов или количество строк).
    • Следите за высокой памятью или переполнением на диск; при необходимости отрегулируйте порядок соединения или используйте rollup-представления.
    • Используйте материализованные представления и query hint (BROADCAST, SHUFFLE, COLOCATE) для оптимизации по мере необходимости.
  3. Операции сканирования: Ищите OlapScanNode или аналогичные. Обратите внимание, какие таблицы сканируются, какие фильтры применяются и используются ли предварительная агрегация или материализованные представления.

  4. Операции соединения: Определите типы соединений (HASH JOIN, BROADCAST, SHUFFLE, COLOCATE, BUCKET SHUFFLE). Метод соединения влияет на производительность:

    • Broadcast: Маленькая таблица отправляется на все узлы; хорошо для маленьких таблиц.
    • Shuffle: Строки секционируются и перемешиваются; хорошо для больших таблиц.
    • Colocate: Таблицы секционированы одинаково; позволяет выполнять локальные соединения.
    • Bucket Shuffle: Перемешивается только одна таблица для сокращения сетевых затрат.
  5. Агрегация и сортировка: Ищите AGGREGATE, TOP-N или ORDER BY. Эти операции могут быть дорогостоящими при больших данных или данных с высокой мощностью.

  6. Перемещение данных: Узлы EXCHANGE показывают передачу данных между фрагментами или узлами. Слишком большое перемещение данных может снизить производительность.

  7. Проталкивание предикатов: Фильтры, примененные рано (при сканировании), сокращают нижестоящие данные. Проверьте PREDICATES или PushdownPredicates, чтобы увидеть, какие фильтры проталкиваются вниз.

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

подсказка

Это запрос 96 из бенчмарка TPC-DS.

explain logical
select count(*)
from store_sales
,household_demographics
,time_dim
, store
where ss_sold_time_sk = time_dim.t_time_sk
and ss_hdemo_sk = household_demographics.hd_demo_sk
and ss_store_sk = s_store_sk
and time_dim.t_hour = 8
and time_dim.t_minute >= 30
and household_demographics.hd_dep_count = 5
and store.s_store_name = 'ese'
order by count(*) limit 100;

Вывод представляет собой иерархический план, показывающий, как Selena будет выполнять запрос. План структурирован как дерево операторов, читаемое снизу вверх. Логический план показывает последовательность операций с оценкой затрат:

- Output => [69:count]
- TOP-100(FINAL)[69: count ASC NULLS FIRST]
Estimates: {row: 1, cpu: 8.00, memory: 8.00, network: 8.00, cost: 68669801.20}
- TOP-100(PARTIAL)[69: count ASC NULLS FIRST]
Estimates: {row: 1, cpu: 8.00, memory: 8.00, network: 8.00, cost: 68669769.20}
- AGGREGATE(GLOBAL) []
Estimates: {row: 1, cpu: 8.00, memory: 8.00, network: 0.00, cost: 68669737.20}
69:count := count(69:count)
- EXCHANGE(GATHER)
Estimates: {row: 1, cpu: 8.00, memory: 0.00, network: 8.00, cost: 68669717.20}
- AGGREGATE(LOCAL) []
Estimates: {row: 1, cpu: 3141.35, memory: 0.80, network: 0.00, cost: 68669701.20}
69:count := count()
- HASH/INNER JOIN [9:ss_store_sk = 40:s_store_sk] => [71:auto_fill_col]
Estimates: {row: 3490, cpu: 111184.52, memory: 8.80, network: 0.00, cost: 68668128.93}
71:auto_fill_col := 1
- HASH/INNER JOIN [7:ss_hdemo_sk = 25:hd_demo_sk] => [9:ss_store_sk]
Estimates: {row: 19940, cpu: 1841177.20, memory: 2880.00, network: 0.00, cost: 68612474.92}
- HASH/INNER JOIN [4:ss_sold_time_sk = 30:t_time_sk] => [7:ss_hdemo_sk, 9:ss_store_sk]
Estimates: {row: 199876, cpu: 69221191.15, memory: 7077.97, network: 0.00, cost: 67671726.32}
- SCAN [store_sales] => [4:ss_sold_time_sk, 7:ss_hdemo_sk, 9:ss_store_sk]
Estimates: {row: 5501341, cpu: 66016092.00, memory: 0.00, network: 0.00, cost: 33008046.00}
partitionRatio: 1/1, tabletRatio: 192/192
predicate: 7:ss_hdemo_sk IS NOT NULL
- EXCHANGE(BROADCAST)
Estimates: {row: 1769, cpu: 7077.97, memory: 7077.97, network: 7077.97, cost: 38928.81}
- SCAN [time_dim] => [30:t_time_sk]
Estimates: {row: 1769, cpu: 21233.90, memory: 0.00, network: 0.00, cost: 10616.95}
partitionRatio: 1/1, tabletRatio: 5/5
predicate: 33:t_hour = 8 AND 34:t_minute >= 30
- EXCHANGE(BROADCAST)
Estimates: {row: 720, cpu: 2880.00, memory: 2880.00, network: 2880.00, cost: 14400.00}
- SCAN [household_demographics] => [25:hd_demo_sk]
Estimates: {row: 720, cpu: 5760.00, memory: 0.00, network: 0.00, cost: 2880.00}
partitionRatio: 1/1, tabletRatio: 1/1
predicate: 28:hd_dep_count = 5
- EXCHANGE(BROADCAST)
Estimates: {row: 2, cpu: 8.80, memory: 8.80, network: 8.80, cost: 44.15}
- SCAN [store] => [40:s_store_sk]
Estimates: {row: 2, cpu: 17.90, memory: 0.00, network: 0.00, cost: 8.95}
partitionRatio: 1/1, tabletRatio: 1/1
predicate: 45:s_store_name = 'ese'

Чтение плана снизу вверх

План запроса следует читать снизу (листовые узлы) вверх к верху (корневой узел), следуя потоку данных:

  1. Операции сканирования (нижний уровень): Операторы SCAN внизу читают данные из базовых таблиц:

    • SCAN [store_sales] читает основную таблицу фактов с предикатом ss_hdemo_sk IS NOT NULL
    • SCAN [time_dim] читает таблицу измерения времени с предикатами t_hour = 8 AND t_minute >= 30
    • SCAN [household_demographics] читает таблицу демографии с предикатом hd_dep_count = 5
    • SCAN [store] читает таблицу магазинов с предикатом s_store_name = 'ese'

    Каждая операция сканирования показывает:

    • Estimates: Оценка количества строк, CPU, памяти, сети и затрат
    • Соотношения секций и tablet: Сколько секций/tablet сканируется (например, partitionRatio: 1/1, tabletRatio: 192/192)
    • Predicates: Условия запроса, которые проталкиваются до уровня сканирования, сокращая объем считываемых данных
  2. Обмен данными (Broadcast): Операции EXCHANGE(BROADCAST) распределяют меньшие таблицы измерений на все узлы, обрабатывающие большую таблицу фактов. Это эффективно, когда таблицы измерений малы по сравнению с таблицей фактов, как видно с time_dim, household_demographics и store, которые транслируются.

  3. Операции соединения (средний уровень): Данные поступают вверх через операции HASH/INNER JOIN:

    • Сначала store_sales соединяется с time_dim по ss_sold_time_sk = t_time_sk
    • Затем результат соединяется с household_demographics по ss_hdemo_sk = hd_demo_sk
    • Наконец, результат соединяется с store по ss_store_sk = s_store_sk

    Каждое соединение показывает условие соединения и оценки для результирующего количества строк и использования ресурсов.

  4. Агрегация (верхний уровень):

    • AGGREGATE(LOCAL) выполняет локальную агрегацию на каждом узле, вычисляя count()
    • EXCHANGE(GATHER) собирает результаты со всех узлов
    • AGGREGATE(GLOBAL) объединяет локальные результаты в окончательный подсчет
  5. Финальные операции (верхний уровень):

    • Операции TOP-100(PARTIAL) и TOP-100(FINAL) обрабатывают предложение ORDER BY count(*) LIMIT 100, выбирая верхние 100 результатов после упорядочивания

Логический план предоставляет оценки затрат для каждой операции, помогая вам понять, где запрос тратит большую часть своих ресурсов. Фактический физический план выполнения (из EXPLAIN или EXPLAIN VERBOSE) включает дополнительные детали о том, как операции распределяются по узлам и выполняются параллельно.