Лучшие практики векторизованного выполнения

В этом разделе описываются способы эффективного использования векторизованного движка выполнения.

1. Сценарии

Степень улучшения производительности векторизованного движка по сравнению с традиционным строковым выполнением различается в зависимости от сценария. Основное представление см. в разделе обзора.

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

2. Типы данных

2.1 Используйте примитивные типы, когда это возможно

Используйте примитивные типы, такие как int32, int64 и т.д., чтобы максимизировать производительность векторизации.

Поля, такие как перечисления (например, страна или китайская провинция), часто представляются в виде строк (например, "Китай", "США", "Пекин", "Синьцзян"). Однако предварительная обработка этих данных в компактные целочисленные типы, такие как smallint, может значительно улучшить производительность операций фильтрации.

2.2 Используйте тип с наименьшим размером, покрывающим диапазон значений

Если столбец, например, "возраст", может быть представлен с помощью smallint, избегайте использования int32 или int64.

В традиционных движках выполнения все числовые типы обычно хранятся как 64-битные значения Datum в кортежах. Datum представляет любое значение SQL-типа данных, но не содержит информации о типе. Таким образом, выбор числового типа оказывает незначительное влияние на скорость обработки.

Однако в векторизованном движке выполнения данные хранятся в плотно упакованных, непрерывных структурах памяти. Более короткие числовые типы лучше используют кэш и позволяют SIMD-регистрам содержать больше элементов. Фильтрация или агрегация столбца с использованием int64 может быть до четырех раз медленнее, чем использование int16.

3. Анализ и настройка производительности

3.1 Обеспечьте чистое векторизованное выполнение

Примечание!
Из-за ограничений по времени и ресурсам векторизованный движок не поддерживает все стандартные SQL-типы данных, выражения и запросы. Для обеспечения работоспособности неподдерживаемые операции переключаются на традиционный движок выполнения. Это может привести к отсутствию или минимальному приросту производительности.

Вы можете отключить параметр matrix.enable_mxv_fallback_expression, чтобы проверить, использует ли запрос традиционный движок. Этот параметр включен по умолчанию.

Параметр Описание
matrix.enable_mxv_fallback_expression Включает переключение на традиционный движок выполнения для выражений, не поддерживаемых векторизованным движком. По умолчанию: включено
$ SET matrix.enable_mxv_fallback_expression TO off;

Когда matrix.enable_mxv_fallback_expression отключен и возникает следующая ошибка:

$ not implemented yet

это означает, что запрос содержит типы данных, выражения или операции, не поддерживаемые текущей версией векторизованного движка.

3.2 Hash Agg против Group Agg

Hash Agg и Group Agg — две реализации оператора агрегации, каждая из которых подходит для разных сценариев. Hash Agg использует хеш-таблицу для агрегации, в то время как Group Agg выполняет агрегацию над предварительно отсортированными группами.

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

Параметр Описание
matrix.enable_mxv_aggregate Управляет включением Group Agg. По умолчанию: включено
matrix.enable_mxv_hash_aggregate Управляет включением Hash Agg. По умолчанию: включено

Эти параметры дают четыре комбинации:

  • Оба отключены: используется традиционный движок выполнения для агрегации.
  • Оба включены: оптимизатор выбирает наилучший путь.
  • matrix.enable_mxv_aggregate включен, matrix.enable_mxv_hash_aggregate выключен: принудительное использование Group Agg.
  • matrix.enable_mxv_hash_aggregate включен, matrix.enable_mxv_aggregate выключен: принудительное использование Hash Agg.

3.3 Комбинирование векторизации с движками хранения MARS2 и MARS3

3.3.1 Выбор ключей сортировки для MARS2 и MARS3

В MARS2 и MARS3 данные хранятся в отсортированном порядке. Чтобы максимизировать преимущества от упорядоченности данных, выбирайте в качестве ключей сортировки часто используемые в фильтрации столбцы с высокой селективностью (то есть столбцы, участвующие в порядке сортировки). Например, в таблице мониторинга устройств используйте временну́ю метку события и идентификатор устройства в качестве ключей сортировки.

Ключи сортировки в MARS2 и MARS3 можно задать только один раз при создании таблицы. Их нельзя изменять, добавлять или удалять.

Различия:

  • MARS2: Порядок сортировки определяется созданием индекса.
  • MARS3: Порядок сортировки определяется указанием столбцов сортировки (разрешено несколько) при создании таблицы.
    • Для ключей сортировки текстового типа, если приемлемо байтовое упорядочение, использование COLLATE C может ускорить сортировку.
    • SQL-ключевое слово для указания ключей сортировки: ORDER BY.

Пример DDL для MARS2 с ключами сортировки:

=# CREATE TABLE t (
    tag int,
    ts timestamp,
    name text,
    region int
   ) USING MARS2;
=# CREATE INDEX ON t USING mars2_btree (ts, tag);

Пример DDL для MARS3 с ключами сортировки:

=# CREATE TABLE t (
    tag int,
    ts timestamp,
    name text,
    region int
    ) USING MARS3
    ORDER BY (ts, tag);

Выбор ключей сортировки критически важен для производительности запросов как в MARS2, так и в MARS3. Упорядоченные данные ускоряют сканирование, фильтрацию на уровне блоков в движке хранения и повышают производительность сортировки и агрегации.

Например, если ключ сортировки таблицы — (c1,c2), то внутренние данные упорядочены по (c1,c2). Рассмотрим два запроса, включающих только сортировку:

=# SELECT * FROM t ORDER BY c1, c2;
=# SELECT * FROM t ORDER BY c1, c3;

Первый запрос не требует сортировки — векторизованный движок исключает оператор Sort. Для второго, поскольку c1 уже отсортирован, движок применяет специальную оптимизацию: он сортирует только значения c3 внутри одной группы c1.

Для запросов только с агрегацией:

=# SELECT sum(c1), sum(c2) FROM t GROUP BY c1, c2;
=# SELECT sum(c1), sum(c3) FROM t GROUP BY c1, c3;

Если используется Group Agg, производительность значительно возрастает — аналогично сортировке — поскольку агрегация может выполняться во время последовательного сканирования. Для Hash Agg порядок данных имеет мало значения. Поскольку последовательные операторы наиболее выгодно используют векторизацию, Group Agg обычно превосходит Hash Agg, особенно для первого запроса.

В итоге: проанализируйте свою рабочую нагрузку и выбирайте ключи сортировки на основе шаблонов запросов (столбцы, используемые в сортировке, агрегации и фильтрации).

3.3.2 MinMax-индекс MARS2 и BRIN-индекс MARS3

MARS2 и MARS3 хранят отсортированные данные в непрерывных сегментах, называемых Runs. Метаданные каждого Run включают минимальные и максимальные значения, используемые для фильтрации при выполнении запросов. Поскольку фильтрация использует метаданные вместо загрузки фактических данных, накладные расходы на ввод-вывод снижаются по сравнению с полным последовательным сканированием.

  • MARS2: Фильтрация minmax включается явным включением minmax-кодирования при создании таблицы. Значения min/max по умолчанию не записываются.
  • MARS3: Использует BRIN-индексы, создаваемые отдельно, для фильтрации на уровне блоков.

Примеры:

Таблица MARS2 с minmax-кодированием на всех столбцах (на практике можно выбрать выборочно):

=# CREATE TABLE t (
    tag int encoding (minmax),
    ts timestamp encoding (minmax),
    name text encoding (minmax),
    region int encoding (minmax)
    ) USING MARS2;
=# CREATE INDEX ON t USING mars2_btree(tag);

Таблица MARS3 с BRIN-индексом:

=# CREATE INDEX idx_mars3 ON t USING mars3_brin(ts, tag);

Запрос для извлечения данных из определенного региона в текущем году:

=# SELECT * FROM t WHERE ts > '2022-01-01' AND region = 2;

MinMax (или BRIN) индексы не только позволяют выполнять фильтрацию на уровне блоков в движке хранения, но и ускоряют векторизованную обработку.

С точки зрения движка хранения: если максимальное значение столбца ts в блоке меньше 2022-01-01, блок пропускается, что экономит I/O и снижает вычислительную нагрузку.

Векторизованный движок использует метаданные minmax/BRIN для оптимизации вычислений. Например, если все значения region в блоке равны 2, движок применяет предикат ts > '2022-01-01' напрямую, без выполнения каких-либо дополнительных операций, связанных с region, что повышает производительность.

MinMax/BRIN-индексы и ключи сортировки настраиваются независимо. Первые могут применяться к любому столбцу; вторые — обычно к нескольким. Их влияние на фильтрацию и сортировку пересекается, но различается. Используйте оба стратегически для максимальной выгоды.

3.3.3 Отключение сканирования по индексу

MARS2 и MARS3 поддерживают сканирование по индексу. Однако текущий векторизованный движок выполнения интегрирован только с minmax/BRIN-индексами и последовательным сканированием — но не со сканированием по индексу. В результате некоторые запросы могут не использовать векторизованное выполнение даже при его включении. План выполнения будет содержать Custom Scan (sortheapscan).

Чтобы принудительно включить векторизованное выполнение, отключите сканирование по индексу:

$ SET enable_indexscan TO off;

Подробнее о MARS2 и MARS3 см. Движок хранения.

3.4 Векторизация с оптимизатором ORCA

Примечание!
Оптимизатор ORCA в настоящее время поддерживает только таблицы HEAP и AO. MARS2/3 не поддерживаются.
При использовании с векторизацией поддерживаются только таблицы AOCO.

Включите оптимизатор ORCA вручную. optimizer имеет сессионную область действия; его необходимо повторно включать после переподключения или запуска новой сессии.

=# SET optimizer TO on;

После включения optimizer и сбора точной статистики по таблицам векторизация автоматически интегрируется с ORCA для генерации оптимизированных планов запросов. Пример:

=# EXPLAIN SELECT * FROM t;
                QUERY PLAN
-------------------------------------------
 Result  (cost=0.00..0.00 rows=0 width=20)
   One-Time Filter: false
 Optimizer: Pivotal Optimizer (GPORCA)
(3 rows)

Примечание!
Дополнительные параметры ORCA см. в Параметры конфигурации оптимизатора Greenplum ORCA.