G.4. pgpro_planner — дополнительные преобразования и оптимизации запросов #

G.4.1. Описание #

Расширение pgpro_planner предоставляет преобразования и оптимизации запросов, которые позволяют создавать эффективные планы выполнения запросов и улучшать производительность.

Преобразования представлены ниже:

По умолчанию это расширение отключено, чтобы избежать неявных или неожиданных изменений в планах запросов. Его можно включить, как описано ниже. Каждое преобразование также можно включить или отключить отдельно с помощью соответствующих параметров конфигурации для соответствия определённым требованиям пользователей.

G.4.2. Установка #

Расширение pgpro_planner поставляется вместе с Postgres Pro Enterprise в виде отдельного пакета pgpro-planner-ent-17 (подробная инструкция по установке приведена в Главе 17).

Чтобы включить pgpro_planner, выполните следующие действия:

  1. Загрузите расширение одним из следующих способов:

    • глобально для всех сеансов путём добавления имени библиотеки в переменную shared_preload_libraries в файле postgresql.conf

      shared_preload_libraries = 'pgpro_planner'
    • в текущем сеансе с помощью команды LOAD без добавления имени библиотеки в переменную shared_preload_libraries

      LOAD 'pgpro_planner';
  2. Перезагрузите сервер баз данных, чтобы изменения вступили в силу.

    Чтобы убедиться, что библиотека pgpro_planner установлена правильно, можно выполнить следующую команду:

    SHOW shared_preload_libraries;
  3. Включите расширение, установив для параметра pgpro_planner.enable значение on.

    SET pgpro_planner.enable = 'on';

G.4.3. Преобразование VALUES в ANY #

Обычно стандартный планировщик обрабатывает конструкции x IN (VALUES (...), (...)) неоптимальным способом. Например, если подзапросы содержат такие конструкции, планировщик может переписывать их в предложения JOIN, чтобы избежать повторных сканирований. Это может приводить к неверным решениям оптимизации и неэффективным планам выполнения.

Расширение pgpro_planner может преобразовывать базовые конструкции x IN (VALUES (...), (...)) в конструкции x = ANY([...]). Это преобразование применяется к дереву разбора перед тем, как планировщик начинает создавать план выполнения запроса.

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

  1. Выполняет поиск конструкций x IN (VALUES (...), (...)) в подзапросах.

  2. Пытается извлечь все значения из выражения VALUES.

  3. Строит массив из извлечённых значений.

  4. Создаёт соответствующую конструкцию x = ANY([...]).

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

Это преобразование позволяет планировщику избегать избыточных предложений JOIN и ошибок в оценке количества строк. Для конструкций ANY([...]) планировщик может также создавать планы запросов с любым методом доступа и стратегией соответствия по хешу, которые являются наиболее эффективными для каждого конкретного случая.

Это преобразование можно отключить, установив для параметра pgpro_planner.enable_values_transformation значение off, например, если не используются выражения VALUES .

G.4.3.1. Пример преобразования VALUES #

Этот пример демонстрирует, как преобразование pgpro_planner работает для следующего запроса:

EXPLAIN (COSTS FALSE) SELECT * FROM onek
  WHERE unique1 IN (VALUES(1200), (1));

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

Nested Loop
  ->  Unique
        ->  Sort
              Sort Key: "*VALUES*".column1
              ->  Values Scan on "*VALUES*"
  ->  Index Scan using onek_unique1 on onek
        Index Cond: (unique1 = "*VALUES*".column1)
(7 rows)

Для выражения VALUES планировщик создаёт соответствующую временную таблицу и выполняет сравнения значений с помощью узлов Nested Loop и Index Cond. Эти решения приводят к долгому времени выполнения запроса.

Когда pgpro_planner преобразовывает выражение VALUES, результирующий план для того же запроса использует более эффективный узел Bitmap Heap Scan вместо Nested Loop, что сокращает время выполнения запроса.

Bitmap Heap Scan on onek
  Recheck Cond: (unique1 = ANY ('{1200,1}'::integer[]))
  ->  Bitmap Index Scan on onek_unique1
        Index Cond: (unique1 = ANY ('{1200,1}'::integer[]))
(4 rows)

G.4.3.2. Ограничения преобразования VALUES #

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

  • Выражение содержит изменяемые функции.

  • Выражение содержит значения, отличные от констант, например VALUES (0), (unique1).

  • Выражение содержит значения сложных типов данных, например VALUES (1,1), (20,0).

  • Выражение содержит значения NULL.

  • Выражение содержит предложения LIMIT, OFFSET или ORDER BY.

  • Выражение используется в подзапросе, который вложен в другой подзапрос.

  • Отсутствует оператор для результирующих переменной и массива x = ANY([...]).

G.4.4. Упрощение тривиальных арифметических операций #

Даже для тривиальных арифметических операций, таких как x + 0, стандартный планировщик предсказывает количество строк и избирательность, как для выражений, и не может использовать индексы в планах выполнения запросов.

Расширение pgpro_planner может упрощать и переписывать следующие базовые арифметические операции:

  • x + 0x

  • x - 0x

  • x * 1x

  • x / 1x

  • x * 0x (только если x не NULL)

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

Это упрощение можно отключить, установив для параметра pgpro_planner.enable_simplify_trivials значение off, например, в целях отладки.

G.4.4.1. Пример арифметического упрощения #

Этот пример демонстрирует, как арифметическое упрощение pgpro_planner затрагивает планы выполнения для следующего запроса:

SELECT * FROM t WHERE x + 0 > 900;

Для исходной арифметической операции планировщик создаёт план выполнения с узлом Seq Scan и не использует индекс.

Seq Scan on t  (cost=0.00..20.00 rows=333 width=4) (actual time=0.500..0.571 rows=100.00 loops=1)
  Filter: ((x + 0) > 900)
  Rows Removed by Filter: 900
  Buffers: shared hit=5
Planning:
  Buffers: shared hit=14
Planning Time: 0.432 ms
Execution Time: 0.627 ms
(8 rows)

Когда pgpro_planner упрощает такую операцию, планировщик создаёт план с узлом Index Only Scan и выполняет сравнения значений в узле Index Cond, что помогает сократить время выполнения запроса.

Index Only Scan using idx_t_x on t  (cost=0.28..10.03 rows=100 width=4) (actual time=0.077..0.247 rows=100.00 loops=1)
  Index Cond: (x > 900)
  Heap Fetches: 100
  Index Searches: 1
  Buffers: shared hit=4
Planning Time: 0.217 ms
Execution Time: 0.319 ms
(7 rows)

G.4.4.2. Ограничения арифметического упрощения #

pgpro_planner имеет следующие ограничения для арифметического упрощения:

  • Поддерживаются только следующие операции: x + 0, x - 0, x * 1, x / 1 и x * 0 (если x не NULL).

  • Поддерживаются только следующие типы данных для констант: int2, int4, float и numeric.

  • Не могут быть упрощены операции, которые используют сложные выражения для получения 0 или 1, например x - 100 - 100.

  • Не могут быть упрощены операции, которые используют изменяемые функции.

G.4.5. Кеширование связанных подзапросов #

Связанные подзапросы используют значения из внешних запросов, например, через предложение WHERE. Эти подзапросы выполняются один раз для каждой строки внешнего запроса, поэтому они могут сильно влиять на производительность.

Расширение pgpro_planner может кешировать результаты параметризованных связанных подзапросов с помощью добавления узлов Memoize в планы выполнения запросов. Это позволяет пропускать идентичные сканирования для одних и тех же параметров и улучшать производительность.

Эта функциональность включена, если для глобального параметра конфигурации enable_memoize и для параметра pgpro_planner.memoize_subplan расширения pgpro_planner установлены значения on (по умолчанию).

pgpro_planner обрабатывает запрос во время стадии создания плана и выполняет следующие шаги:

  1. pgpro_planner выполняет поиск поддерживаемых связанных подзапросов.

  2. Если для параметра pgpro_planner.memoize_check_uniqueness установлено значение on (по умолчанию), pgpro_planner проверяет уникальность для значений столбцов, задействованных в подзапросах, на основе статистической информации. Это помогает определить, полезно ли использовать Memoize.

    Узел Memoize может быть добавлен в следующих случаях:

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

    • Существует уникальный индекс по столбцу, но значения столбца содержат значения NULL на основе статистической информации.

    • Существует составной уникальный индекс, но значения задействованного столбца не уникальны на основе статистической информации.

    • Статистическая информация для столбца ещё не собрана независимо от того, существует ли индекс.

  3. pgpro_planner оценивает ожидаемую стоимость результирующего плана с узлом Memoize.

  4. Если ожидаемая стоимость меньше, чем стоимость изначального плана, pgpro_planner добавляет узел Memoize в начало соответствующего подплана.

Кеширование связанных подзапросов можно отключить, установив для параметра pgpro_planner.memoize_subplan значение off, например, если связанные подзапросы редко используются. Чтобы отключить проверку уникальности в целях отладки, установите для параметра pgpro_planner.memoize_check_uniqueness значение off.

G.4.5.1. Пример кеширования подзапросов #

Этот пример демонстрирует, как работает кеширование связанных подзапросов в разных сценариях.

Создайте две простых таблицы с именами t и t1, а также уникальный индекс по столбцу x первой таблицы.

CREATE TABLE t (x int, y int);
INSERT INTO t SELECT id, id%20 FROM generate_series(1,1000) id;
CREATE UNIQUE INDEX t_x_idx ON t (x);
CREATE TABLE t1 (x1 int);
INSERT INTO  t1 SELECT id%2 FROM generate_series(1,1000) id;

Запустите следующий запрос, в котором подзапрос использует значения из связанного столбца с именем y. Уникального индекса по этому столбцу не существует, поэтому узел Memoize добавлен в план выполнения запроса.

EXPLAIN (COSTS OFF)
SELECT x FROM t WHERE (t.x,t.y) = (SELECT sum(x1),1 FROM t1 WHERE x1 = t.y LIMIT 1);
                    QUERY PLAN
------------------------------------------------
  Seq Scan on t
    Filter: (SubPlan 1)
    SubPlan 1
      ->  Memoize
            Cache Key: t.y
            Cache Mode: binary
            ->  Limit
                  ->  Aggregate
                        ->  Seq Scan on t1
                              Filter: (x1 = t.y)
(10 rows)

Запустите другой запрос, в котором подзапрос использует значения из связанного столбца с именем x. Уникальный индекс по этому столбцу существует, но статистическая информация ещё не собрана, поэтому узел Memoize генерируется.

EXPLAIN (COSTS OFF)
SELECT x FROM t WHERE (t.x,t.y) = (SELECT sum(x1),1 FROM t1 WHERE x1 = t.x LIMIT 1);
                    QUERY PLAN
------------------------------------------------
  Seq Scan on t
    Filter: (SubPlan 1)
    SubPlan 1
      ->  Memoize
            Cache Key: t.x
            Cache Mode: binary
            ->  Limit
                  ->  Aggregate
                        ->  Seq Scan on t1
                              Filter: (x1 = t.x)
(10 rows)

Запустите тот же запрос ещё раз. Статистическая информация для столбца x теперь собрана, и уникальный индекс всё ещё существует, поэтому узел Memoize не добавлен в план.

EXPLAIN (COSTS OFF)
SELECT x FROM t WHERE (t.x,t.y) = (SELECT sum(x1),1 FROM t1 WHERE x1 = t.x LIMIT 1);
                            QUERY PLAN
-----------------------------------------------------------------
 Seq Scan on t
   Filter: (x = (SubPlan 1))
   SubPlan 1
     ->  Limit
           ->  Aggregate
                 ->  Seq Scan on t1
                       Filter: (x1 = t.x)
(7 rows)

Удалите ранее созданный индекс и создайте новый составной индекс по обоим столбцам с именами x и y.

CREATE UNIQUE INDEX on t(x,y);
DROP INDEX t_x_idx;

Запустите запрос, ссылающийся на связанный столбец y. Хотя существует составной уникальный индекс, значения этого столбца не уникальны на основе статистической информации. Узел Memoize добавляется в результирующий план.

EXPLAIN (COSTS OFF)
SELECT x FROM t WHERE (t.x,t.y) = (SELECT sum(x1),1 FROM t1 WHERE x1 = t.y LIMIT 1);
                            QUERY PLAN
-----------------------------------------------------------------
  Seq Scan on t
    Filter: (((x = (SubPlan 1).col1) AND (y = (SubPlan 1).col2)))
    SubPlan 1
      ->  Memoize
            Cache Key: t.y
            Cache Mode: binary
            ->  Limit
                  ->  Aggregate
                        ->  Seq Scan on t1
                              Filter: (x1 = t.y)
(10 rows)

Запустите запрос, ссылающийся на связанный столбец x. Для этого случая узел Memoize по-прежнему не генерируется, поскольку значения столбца уникальны на основе статистической информации.

EXPLAIN (COSTS OFF)
SELECT x FROM t WHERE (t.x,t.y) = (SELECT sum(x1),1 FROM t1 WHERE x1 = t.x LIMIT 1);
                            QUERY PLAN
-----------------------------------------------------------------
  Seq Scan on t
    Filter: (((x = (SubPlan 1).col1) AND (y = (SubPlan 1).col2)))
    SubPlan 1
      ->  Limit
            ->  Aggregate
                  ->  Seq Scan on t1
                        Filter: (x1 = t.x)
(7 rows)

G.4.5.2. Ограничения кеширования подзапросов #

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

  • Подзапрос использует изменяемые функции.

  • Подзапрос использует нестандартные операторы.

  • Подзапрос использует операнды с разными типами без совместимых классов операторов хеширования.

  • Подзапрос использует агрегатные функции min или max.

  • Дерево плана запроса не содержит соответствующий узел подплана.

  • Оценённая стоимость целевого плана запроса с узлами Memoize выше, чем стоимость изначального плана.

G.4.6. Параметры конфигурации #

pgpro_planner.enable (boolean) #

Включает или отключает расширение pgpro_planner. Значение по умолчанию — off, чтобы избежать неявных или неожиданных изменений в планах запросов.

pgpro_planner.enable_values_transformation (boolean) #

Включает или отключает преобразование конструкций x IN (VALUES (...), (...)) в конструкции x = ANY([...]). Значение по умолчанию — on.

pgpro_planner.enable_simplify_trivials (boolean) #

Включает или отключает упрощение тривиальных арифметических операций. Значение по умолчанию — on.

pgpro_planner.memoize_subplan (boolean) #

Включает или отключает кеширование связанных подзапросов. Значение по умолчанию — on.

pgpro_planner.memoize_check_uniqueness (boolean) #

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

pgpro_planner.force_memoize_subplan (boolean) #

Включает или отключает автоматическое добавление узла Memoize для всех связанных подзапросов, игнорируя модель стоимости планов и проверку уникальности. Этот параметр предназначен только для целей отладки, не используйте его в производственной среде. Значение по умолчанию — off.