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

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

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

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

  • упрощение операций x + 0, x - 0, x * 1, x / 1 и x * 0 до x

  • кеширование результатов связанных подзапросов с помощью узлов Memoize

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

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

Расширение pgpro_planner поставляется вместе с Postgres Pro Enterprise в виде отдельного пакета pgpro-planner-ent-18 (подробная инструкция по установке приведена в Главе 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. Упрощение тривиальных арифметических операций #

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

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

  • x + 0x

  • x - 0x

  • x * 1x

  • x / 1x

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

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

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

G.4.3.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.3.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.4. Кеширование связанных подзапросов #

Связанные подзапросы используют значения из внешних запросов, например, через предложение 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.4.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.4.2. Ограничения кеширования подзапросов #

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

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

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

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

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

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

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

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

pgpro_planner.enable (boolean) #

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

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.