F.25. hypopg — поддержка гипотетических индексов #

F.25.1. Описание #

hypopg — это расширение Postgres Pro, добавляющее поддержку гипотетических индексов.

Гипотетический или виртуальный индекс — это индекс, который на самом деле не существует, поэтому на его создание не расходуются ресурсы процессора, диска и прочие. Такие индексы помогают без затрат ресурсов выяснить, можно ли повысить производительность проблемных запросов с помощью каких-либо индексов, а также узнать, будет ли Postgres Pro использовать эти индексы или нет.

F.25.2. Установка #

Расширение hypopg поставляется вместе с Postgres Pro в виде отдельного пакета hypopg-ent-16 (подробные инструкции по установке приведены в Главе 16). После установки Postgres Pro создайте расширение hypopg:

CREATE EXTENSION hypopg;

Расширение hypopg теперь доступно. Это можно проверить с помощью psql:

\dx
                     List of installed extensions
  Name   | Version |   Schema   |             Description
---------+---------+------------+-------------------------------------
 hypopg  | 1.4.1   | public     | Hypothetical indexes for Postgres Pro
 plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
(2 rows)

Видно, что расширение hypopg установлено.

F.25.3. Функции #

Расширение hypopg можно использовать, чтобы проверить, поможет ли какой-либо индекс выполнению одного или нескольких запросов. Поэтому вам должно быть уже известно, какие запросы нужно оптимизировать и какие индексы попробовать.

Гипотетические индексы, которые создаёт hypopg, хранятся не в каталоге, а в собственной памяти вашего соединения. Таким образом, это не раздувает таблицы и не влияет на параллельные соединения.

Кроме того, поскольку гипотетические индексы фактически не существуют, hypopg гарантирует, что они будут использоваться только в обычном операторе EXPLAIN (без параметра ANALYZE).

Поддерживаются следующие методы доступа:

  • btree

  • brin

  • hash

  • bloom (требуется наличие установленного расширения bloom)

Примечание

Использование hypopg требует некоторых знаний о команде EXPLAIN.

F.25.3.1. Создание гипотетического индекса #

hypopg_create_index() #

Рассмотрим простой пример:

CREATE TABLE hypo (id integer, val text) ;
INSERT INTO hypo SELECT i, 'line ' || i FROM generate_series(1, 100000) i ;
VACUUM ANALYZE hypo ;

У этой таблицы нет индекса. Предположим, мы хотим проверить, поможет ли индекс простому запросу. Сначала посмотрим, как он себя ведёт:

EXPLAIN SELECT val FROM hypo WHERE id = 1;
                       QUERY PLAN
--------------------------------------------------------
 Seq Scan on hypo  (cost=0.00..1791.00 rows=1 width=14)
   Filter: (id = 1)
(2 rows)

Используется простое последовательное сканирование, поскольку индекса в таблице нет. Построение индекса B-дерева по столбцу id должно помочь этому запросу. Проверим это с помощью hypopg. Функция hypopg_create_index() примет любые стандартные операторы CREATE INDEX (любые другие операторы, переданные этой функции, будут проигнорированы) и создаст гипотетический индекс для каждого:

SELECT * FROM hypopg_create_index('CREATE INDEX ON hypo (id)') ;
 indexrelid |      indexname
------------+----------------------
      18284 | <18284>btree_hypo_id
(1 row)

Функция возвращает два столбца:

  • идентификатор объекта гипотетического индекса

  • сгенерированное имя гипотетического индекса

Можно снова запустить EXPLAIN, чтобы увидеть, будет ли Postgres Pro использовать этот индекс:

EXPLAIN SELECT val FROM hypo WHERE id = 1;
                                    QUERY PLAN
----------------------------------------------------------------------------------
 Index Scan using <18284>btree_hypo_id on hypo  (cost=0.04..8.06 rows=1 width=10)
   Index Cond: (id = 1)
(2 rows)

Да, Postgres Pro будет использовать такой индекс. Теперь нужно убедиться, что гипотетический индекс не будет использоваться для фактического выполнения запроса:

EXPLAIN ANALYZE SELECT val FROM hypo WHERE id = 1;
                                            QUERY PLAN
---------------------------------------------------------------------------------------------------
 Seq Scan on hypo  (cost=0.00..1791.00 rows=1 width=10) (actual time=0.046..46.390 rows=1 loops=1)
   Filter: (id = 1)
   Rows Removed by Filter: 99999
 Planning time: 0.160 ms
 Execution time: 46.460 ms
(5 rows)

Это всё, что необходимо для создания гипотетических индексов и проверки, будет ли Postgres Pro использовать такие индексы.

F.25.3.2. Управление гипотетическими индексами #

В представлении hypopg_list_indexes перечислены все созданные гипотетические индексы.

SELECT * FROM hypopg_list_indexes ;
 indexrelid |      index_name       | schema_name | table_name | am_name
------------+-----------------------+-------------+------------+---------
      18284 | <18284>btree_hypo_id  | public      | hypo       | btree
(1 row)
hypopg() #

Функция hypopg() выводит список всех гипотетических индексов, созданных в том же формате, что и pg_index.

SELECT * FROM hypopg() ;
      indexname       | indexrelid | indrelid | innatts | indisunique | indkey | indcollation | indclass | indoption | indexprs | indpred | amid
----------------------+------------+----------+---------+-------------+--------+--------------+----------+-----------+----------+---------+------
 <18284>btree_hypo_id |      13543 |    18122 |       1 | f           | 1      | 0            | 1978     | <NULL>    | <NULL>   | <NULL>  |  403
(1 row)
hypopg_get_indexdef(oid) #

Функция hypopg_get_indexdef(oid) перечисляет операторы CREATE INDEX, которые воссоздадут сохранённый гипотетический индекс.

SELECT index_name, hypopg_get_indexdef(indexrelid) FROM hypopg_list_indexes ;
      index_name       |             hypopg_get_indexdef
-----------------------+----------------------------------------------
 <18284>btree_hypo_id  | CREATE INDEX ON public.hypo USING btree (id)
(1 row)
hypopg_relation_size(oid) #

Функция hypopg_relation_size(oid) вычисляет размер гипотетического индекса:

SELECT index_name, pg_size_pretty(hypopg_relation_size(indexrelid))
  FROM hypopg_list_indexes ;
      index_name       | pg_size_pretty
-----------------------+----------------
 <18284>btree_hypo_id  | 2544 kB
(1 row)
hypopg_drop_index(oid) #

Функция hypopg_drop_index(oid) удаляет указанный гипотетический индекс.

hypopg_reset() #

Функция hypopg_reset() удаляет все гипотетические индексы.

F.25.3.3. Гипотетическое скрытие существующих индексов #

hypopg_hide_index(oid) #

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

В качестве примера рассмотрим два индекса:

SELECT hypopg_reset();
CREATE INDEX ON hypo(id);
CREATE INDEX ON hypo(id, val);
EXPLAIN SELECT * FROM hypo WHERE id = 1;
                                    QUERY PLAN
----------------------------------------------------------------------------------
Index Only Scan using hypo_id_val_idx on hypo  (cost=0.29..8.30 rows=1 width=13)
Index Cond: (id = 1)
(2 rows)

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

Функция hypopg_hide_index(oid) позволяет скрыть индекс в выводе EXPLAIN, используя его OID. Возвращает true, если индекс был успешно скрыт, и false в противном случае.

SELECT hypopg_hide_index('hypo_id_val_idx'::REGCLASS);
 hypopg_hide_index
-------------------
t
(1 row)

EXPLAIN SELECT * FROM hypo WHERE id = 1;
                            QUERY PLAN
-------------------------------------------------------------------------
Index Scan using hypo_id_idx on hypo  (cost=0.29..8.30 rows=1 width=13)
Index Cond: (id = 1)
(2 rows)

В качестве примера предположим, что план запроса в данный момент использует индекс hypo_id_val_idx. Для продолжения тестирования используйте функцию hypopg_hide_index(oid), чтобы скрыть другой индекс.

SELECT hypopg_hide_index('hypo_id_idx'::REGCLASS);
 hypopg_hide_index
-------------------
t
(1 row)

EXPLAIN SELECT * FROM hypo WHERE id = 1;
                    QUERY PLAN
-------------------------------------------------------
Seq Scan on hypo  (cost=0.00..180.00 rows=1 width=13)
Filter: (id = 1)
(2 rows)
hypopg_unhide_index(oid) #

Функция hypopg_unhide_index(oid) восстанавливает ранее скрытый индекс в выводе EXPLAIN, используя его OID. Возвращает true, если индекс был успешно восстановлен, и false в противном случае.

SELECT hypopg_unhide_index('hypo_id_idx'::regclass);
 hypopg_unhide_index
-------------------
t
(1 row)

EXPLAIN SELECT * FROM hypo WHERE id = 1;
                            QUERY PLAN
-------------------------------------------------------------------------
Index Scan using hypo_id_idx on hypo  (cost=0.29..8.30 rows=1 width=13)
Index Cond: (id = 1)
(2 rows)
hypopg_unhide_all_index(oid) #

Функция hypopg_unhide_all_index() восстанавливает все скрытые индексы и возвращает void.

hypopg_hidden_indexes() #

Функция hypopg_hidden_indexes() возвращает список OID для всех скрытых индексов.

SELECT * FROM hypopg_hidden_indexes();
 indexid
---------
526604
(1 rows)

В представлении hypopg_hidden_indexes содержится форматированный список всех скрытых индексов.

SELECT * FROM hypopg_hidden_indexes;
  indexrelid |      index_name      | schema_name | table_name | am_name | is_hypo
-------------+----------------------+-------------+------------+---------+---------
      526604 | hypo_id_val_idx      | public      | hypo       | btree   | f
(1 rows)

Примечание

Гипотетические индексы также можно скрывать.

SELECT hypopg_create_index('CREATE INDEX ON hypo(id)');
    hypopg_create_index
------------------------------
(12659,<12659>btree_hypo_id)
(1 row)

EXPLAIN SELECT * FROM hypo WHERE id = 1;
                                    QUERY PLAN
------------------------------------------------------------------------------------
Index Scan using "<12659>btree_hypo_id" on hypo  (cost=0.04..8.05 rows=1 width=13)
Index Cond: (id = 1)
(2 rows)

Теперь, когда используется гипотетический индекс, можно попробовать скрыть его, чтобы увидеть изменение:

SELECT hypopg_hide_index(12659);
 hypopg_hide_index
-------------------
t
(1 row)

EXPLAIN SELECT * FROM hypo WHERE id = 1;
                            QUERY PLAN
-------------------------------------------------------------------------
Index Scan using hypo_id_idx on hypo  (cost=0.29..8.30 rows=1 width=13)
Index Cond: (id = 1)
(2 rows)

SELECT * FROM hypopg_hidden_indexes;
  indexrelid |      index_name      | schema_name | table_name | am_name | is_hypo
-------------+----------------------+-------------+------------+---------+---------
       12659 | <12659>btree_hypo_id | public      | hypo       | btree   | t
      526604 | hypo_id_val_idx      | public      | hypo       | btree   | f
(2 rows)

Примечание

Если гипотетический индекс был скрыт, он будет автоматически раскрыт при удалении функцией hypopg_drop_index(oid) или hypopg_reset().

SELECT hypopg_drop_index(12659);

SELECT * FROM hypopg_hidden_indexes;
  indexrelid |      index_name      | schema_name | table_name | am_name | is_hypo
-------------+----------------------+-------------+------------+---------+---------
      526604 | hypo_id_val_idx      | public      | hypo       | btree   | f
(2 rows)

F.25.4. Параметры GUC #

Доступны следующие параметры конфигурации (GUC), которые можно изменять интерактивно:

hypopg.enabled #

По умолчанию включён (on). Используйте этот параметр для глобального включения или отключения hypopg. Если расширение отключено, гипотетические индексы не будут использоваться, но уже определённые гипотетические индексы не будут удалены.

hypopg.use_real_oids #

По умолчанию выключен (off). При таком значении hypopg не будет использовать «реальные» идентификаторы объектов, а вместо этого позаимствует их из диапазона ~ 14000 / 16384 (соответственно наименьший неиспользуемый OID меньше FirstNormalObjectId и FirstNormalObjectId), которые зарезервированы Postgres Pro для использования в будущих выпусках. Это не вызывает никаких проблем, так как свободный диапазон динамически вычисляется при первом использовании hypopg подключением и расширение может работать на резервных серверах. Однако одновременно может существовать не более 2500 гипотетических индексов, и после превышения максимального количества объектов создание нового гипотетического индекса станет очень медленным, пока не будет вызвана функция hypopg_reset().

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

Обратите внимание, что переключение этого параметра не требует сброса записей.