Лучшие практики векторизации

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

1 Сценарии

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

Например, операторы, которые могут работать в полной последовательной последовательности — такие как вычисления общих выражений, операторы фильтрации и агрегации — способны максимально повысить производительность. Для операторов, вносящих случайность — таких как операторы сортировки (Sort) и хеширования (Hash) — возможности для улучшения производительности ограничены.

2 Типы данных

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

Необходимо максимально использовать примитивные типы, такие как int32, int64 и т.д., чтобы повысить производительность и эффективность векторизации.

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

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

Если поле возраста в данных достаточно представить с помощью smallint, нет необходимости использовать int32 или даже int64.

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

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

3 Анализ и оптимизация производительности

3.1 Используйте только векторизованный движок выполнения

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

Вы можете вручную отключить параметр 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 Aggregation;
  • Включён matrix.enable_mxv_hash_aggregate, отключён matrix.enable_mxv_aggregate: принудительно используется Hash Aggregation.

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

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

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

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

Разница заключается в следующем: при создании таблицы MARS2:

  • Порядок сортировки задаётся через создание индексов.

При создании таблицы MARS3:

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

MARS2 может задать набор ключей сортировки следующим SQL:

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

MARS3 может задать набор ключей сортировки следующим SQL:

=# 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;

Первый запрос уже не требует сортировки — векторизованный движок выполнения оптимизирует оператор сортировки. Во втором запросе 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 часто требует меньше времени для вышеуказанных запросов, особенно для первого.

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

3.3.2 minmax-индекс MARS2 и Brin-индекс MARS3

Согласно ключам сортировки, данные в MARS2 и MARS3 хранятся в упорядоченном виде; непрерывный упорядоченный фрагмент называется Run. Метаданные Run содержат минимальные и максимальные значения, используемые для фильтрации при запросах. Поскольку фильтрация выполняется на основе метаданных, сами данные не загружаются, что снижает I/O-накладные расходы по сравнению с последовательным сканированием (сначала чтение данных, затем фильтрация) и ускоряет фильтрацию.

  • MARS2 включает фильтрацию запросов путём включения minmax-индекса при создании таблицы. Минимальные и максимальные значения по умолчанию не записываются и должны быть явно объявлены.
  • MARS3 реализует фильтрацию путём создания дополнительного Brin-индекса.

Примеры:

Для таблиц MARS2 minmax-индексы создаются на всех столбцах примера DDL (в реальной работе можно создавать по необходимости):

=# 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 пример DDL для создания Brin-индекса:

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

Нам нужно запросить данные из определённого региона за текущий год из вышеуказанной таблицы MARS2 (или MARS3):

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

На самом деле minmax-индекс (Brin-индекс) не только позволяет движку хранения выполнять фильтрацию блоков, но и ускоряет вычисления векторизованного движка выполнения.

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

Векторизованный движок выполнения использует информацию minmax (Brin) для ускорения вычислений. Например, если минимальное и максимальное значения определённого диапазона равны 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 см. Движок хранения