hypopg is a PostgreSQL extension for creating hypothetical indexes — virtual indexes that have no resource cost (CPU, disk, or memory) and are never written to disk. Use it to find out whether a specific index would improve a slow query without waiting for the index to build or consuming server resources.
Prerequisites
Before you begin, ensure that you have:
A PolarDB for PostgreSQL (Compatible with Oracle) cluster running one of the following engine versions:
PolarDB for PostgreSQL (Compatible with Oracle) 2.0, revision version 2.0.14.1.0 or later
PolarDB for PostgreSQL (Compatible with Oracle) 1.0, revision version 1.1.28 or later
Identified the queries you want to optimize and the index types to test
To check the revision version of your cluster, run:
SHOW polar_version;How it works
A hypothetical index is stored in the private memory of your current connection — not in any system table or physical file. Because it does not exist on disk, it works only with EXPLAIN (without the ANALYZE option). Running EXPLAIN ANALYZE executes the query against real data, so the database ignores hypothetical indexes and uses a sequential scan instead.
Supported index types:
| Index type | Access method | Prerequisite |
|---|---|---|
| B-tree index | btree | None |
| Block range index (BRIN) | brin | None |
| Hash index | hash | None |
| Bloom index | bloom | Install the bloom extension first |
Install the extension
Install hypopg:
CREATE EXTENSION hypopg;Verify the installation:
\dx hypopgExpected output:
List of installed extensions Name | Version | Schema | Description --------+---------+--------+------------------------------------- hypopg | 1.3.1 | public | Hypothetical indexes for PostgreSQL (1 row)Alternatively, query the
pg_extensioncatalog:SELECT * FROM pg_extension WHERE extname = 'hypopg';Expected output:
extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition ---------+----------+--------------+----------------+------------+-----------+-------------- hypopg | 10 | 2200 | t | 1.3.1 | | (1 row)
Configure parameters
| Parameter | Default | Description |
|---|---|---|
hypopg.enabled | on | Enables or disables hypopg. When set to off, the database ignores hypothetical indexes but does not delete them. |
hypopg.use_real_oids | off | Controls how object identifiers (OIDs) are assigned to hypothetical indexes. See OID assignment for details. |
OID assignment
By default (hypopg.use_real_oids = off), hypopg borrows OIDs from a reserved range that PostgreSQL sets aside for future use. This range is computed dynamically the first time a connection uses hypopg, and it works on a secondary server. The drawback is a limit of approximately 2,500 hypothetical indexes per connection. Once that limit is reached, creating a new hypothetical index becomes very slow. Call hypopg_reset() to clear all hypothetical indexes and reset the counter.
Set hypopg.use_real_oids = on if you need more than 2,500 hypothetical indexes at the same time. With real OIDs, there is no practical index limit, but hypopg requires more lock resources and cannot be used on a secondary server. Changing this parameter does not reset existing hypothetical indexes — real OIDs and borrowed OIDs can coexist.
Test a hypothetical index
Because hypothetical indexes exist only in the current connection's memory, run the hypopg_create_index() call and the EXPLAIN statement in the same session.
The following example walks through the full workflow on a sample table.
Create a table and populate it:
CREATE TABLE hypo (id integer, val text); INSERT INTO hypo SELECT i, 'line ' || i FROM generate_series(1, 100000) i; VACUUM ANALYZE hypo;Check the baseline query plan. Without any index, the planner uses a sequential scan:
EXPLAIN SELECT val FROM hypo WHERE id = 1;Output:
QUERY PLAN -------------------------------------------------------- Seq Scan on hypo (cost=0.00..1791.00 rows=1 width=10) Filter: (id = 1) (2 rows)Create a hypothetical index on the
idcolumn:SELECT * FROM hypopg_create_index('CREATE INDEX ON hypo (id)');Output:
indexrelid | indexname ------------+---------------------- 13925 | <13925>btree_hypo_id (1 row)hypopg_create_index()accepts any standard CREATE INDEX statement and creates a hypothetical index for each one. The OID shown (13925in this example) is dynamically generated.Run EXPLAIN again to check whether the planner would use the hypothetical index:
EXPLAIN SELECT val FROM hypo WHERE id = 1;Output:
QUERY PLAN ------------------------------------------------------------------------------------ Index Scan using "<13925>btree_hypo_id" on hypo (cost=0.04..8.06 rows=1 width=10) Index Cond: (id = 1) (2 rows)The planner switched from a sequential scan to an index scan, confirming the hypothetical index would help.
Confirm that EXPLAIN ANALYZE ignores the hypothetical index (as expected):
EXPLAIN ANALYZE SELECT val FROM hypo WHERE id = 1;Output:
QUERY PLAN --------------------------------------------------------------------------------------------------- Seq Scan on hypo (cost=0.00..1791.00 rows=1 width=10) (actual time=0.030..15.439 rows=1 loops=1) Filter: (id = 1) Rows Removed by Filter: 99999 Planning Time: 0.066 ms Execution Time: 15.492 ms (5 rows)EXPLAIN ANALYZE executes the query for real, so the hypothetical index is not used.
Manage hypothetical indexes
A quick reference of all hypopg functions and views:
| Function / view | Description |
|---|---|
hypopg_create_index(text) | Creates a hypothetical index from a CREATE INDEX statement |
hypopg_list_indexes | View that lists all hypothetical indexes in the current session |
hypopg() | Lists all hypothetical indexes in the same format as pg_index |
hypopg_get_indexdef(oid) | Returns the CREATE INDEX statement for a hypothetical index |
hypopg_relation_size(oid) | Estimates the size of a hypothetical index |
hypopg_drop_index(oid) | Deletes the hypothetical index with the specified OID |
hypopg_reset() | Deletes all hypothetical indexes in the current session |
List hypothetical indexes
Use the hypopg_list_indexes view:
SELECT * FROM hypopg_list_indexes;Output:
indexrelid | index_name | schema_name | table_name | am_name
------------+----------------------+-------------+------------+---------
13925 | <13925>btree_hypo_id | public | hypo | btree
(1 row)Or use the hypopg() function to get the same fields as pg_index:
SELECT * FROM hypopg();Output:
indexname | indexrelid | indrelid | innatts | indisunique | indkey | indcollation | indclass | indoption | indexprs | indpred | amid
----------------------+------------+----------+---------+-------------+--------+--------------+----------+-----------+----------+---------+------
<13925>btree_hypo_id | 13925 | 16450 | 1 | f | 1 | 0 | 1978 | | | | 403
(1 row)Get the index definition
SELECT index_name, hypopg_get_indexdef(indexrelid) FROM hypopg_list_indexes;Output:
index_name | hypopg_get_indexdef
----------------------+----------------------------------------------
<13925>btree_hypo_id | CREATE INDEX ON public.hypo USING btree (id)
(1 row)Estimate the index size
SELECT index_name, pg_size_pretty(hypopg_relation_size(indexrelid))
FROM hypopg_list_indexes;Output:
index_name | pg_size_pretty
----------------------+----------------
<13925>btree_hypo_id | 2544 kB
(1 row)Delete a hypothetical index
Delete a specific index by OID:
SELECT hypopg_drop_index(13925);Output:
hypopg_drop_index
-------------------
t
(1 row)Delete all hypothetical indexes in the current session:
SELECT hypopg_reset();Remove the extension
DROP EXTENSION hypopg;