All Products
Search
Document Center

PolarDB:hypopg (hypothetical indexes)

Last Updated:Mar 28, 2026

Building an index on a large table can take minutes or hours and consume significant disk, CPU, and lock resources. The hypopg extension is an open source extension that lets you test whether a proposed index would improve a specific query — before committing to building a real index. Hypothetical indexes are free: they exist only in session memory, consume no disk space, and do not affect query execution.

How it works

Hypothetical indexes are stored in the private memory of your connection, not in any system tables. Because they have no physical presence on disk, they are visible only to plain EXPLAIN statements — not to EXPLAIN ANALYZE, which executes the query against real data.

This design gives you an instant answer to "would the planner use this index?" without paying the cost of building it.

Supported index types:

Index typeDescription
btreeB-tree indexes (default)
brinBlock Range Indexes (BRIN)
hashHash indexes
bloomBloom indexes (requires the bloom extension)

Prerequisites

Before you begin, ensure that you have:

  • A PolarDB for PostgreSQL cluster running one of the following minor engine versions:

    • PostgreSQL 16: 2.0.16.9.8.0 or later

    • PostgreSQL 14: 2.0.14.5.1.0 or later

    • PostgreSQL 11: 2.0.11.9.28.0 or later

  • Identified the slow queries to optimize and the index types to try

To check your minor engine version, view it in the console or run SHOW polardb_version;. To upgrade, see Upgrade the minor engine version.

Install the extension

CREATE EXTENSION hypopg;

To verify the installation:

\dx hypopg

Expected output:

List of installed extensions
  Name  | Version | Schema |             Description
--------+---------+--------+-------------------------------------
 hypopg | 1.3.1   | public | Hypothetical indexes for PostgreSQL
(1 row)

Alternatively, query the pg_extension catalog:

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)

Test an index without building it

This example walks through the complete workflow: identify a slow query, create a hypothetical index, confirm the planner would use it, then decide whether to build the real index.

1. Set up a test table.

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

2. Check the current query plan.

EXPLAIN SELECT val FROM hypo WHERE id = 1;

Without an index, the planner falls back to a sequential scan:

QUERY PLAN
--------------------------------------------------------
 Seq Scan on hypo  (cost=0.00..1791.00 rows=1 width=10)
   Filter: (id = 1)
(2 rows)

3. Create a hypothetical index.

SELECT * FROM hypopg_create_index('CREATE INDEX ON hypo (id)');

Output:

indexrelid |      indexname
------------+----------------------
      13925 | <13925>btree_hypo_id
(1 row)

The indexrelid (here 13925) is dynamically assigned. The indexname reflects the index type and columns.

hypopg_create_index() accepts any standard CREATE INDEX statement. Any other statements passed to it are ignored.

4. Verify the planner would use the index.

EXPLAIN SELECT val FROM hypo WHERE id = 1;

The query plan now shows an index scan:

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 cost drops from 1791.00 to 8.06. The planner would use this index.

5. Confirm the index is not used during actual execution.

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.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 real query and ignores hypothetical indexes. This is expected behavior.

Manage hypothetical indexes

The following functions and views are available for managing hypothetical indexes.

NameTypeDescription
hypopg_list_indexesViewLists all hypothetical indexes
hypopg()FunctionLists hypothetical indexes in pg_index format
hypopg_get_indexdef(oid)FunctionReturns the CREATE INDEX statement for a hypothetical index
hypopg_relation_size(oid)FunctionEstimates the size of a hypothetical index
hypopg_drop_index(oid)FunctionDeletes a hypothetical index by object identifier (OID)
hypopg_reset()FunctionDeletes all hypothetical indexes

hypopg_list_indexes

Lists all hypothetical indexes in the current session:

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

hypopg()

Lists hypothetical indexes in the same format as the pg_index system catalog:

SELECT * FROM hypopg();
      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)

hypopg_get_indexdef(oid)

Returns the CREATE INDEX statement that would create the specified hypothetical index:

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

hypopg_relation_size(oid)

Estimates the size of a hypothetical index:

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

hypopg_drop_index(oid)

Deletes a single hypothetical index by OID:

SELECT hypopg_drop_index(13925);
hypopg_drop_index
-------------------
 t
(1 row)

hypopg_reset()

Deletes all hypothetical indexes in the current session:

SELECT hypopg_reset();
hypopg_reset
--------------

(1 row)

Configure the extension

ParameterDefaultDescription
hypopg.enabledonControls whether hypothetical indexes are used by the planner. Set to off to disable without deleting existing hypothetical indexes.
hypopg.use_real_oidsoffControls how object identifiers (OIDs) are assigned to hypothetical indexes. See details below.

hypopg.use_real_oids

off (default): hypopg selects OIDs from a reserved free range, calculated dynamically the first time hypopg is used. This mode works on standby servers. The trade-off is a limit of approximately 2,500 hypothetical indexes at the same time. If you exceed this limit, creating a new hypothetical index becomes slow. Call hypopg_reset() to clear all existing hypothetical indexes and restore normal performance.

on: hypopg requests real OIDs from the database. This removes the 2,500-index limit but requires more lock resources and cannot be used on standby servers.

Switching this parameter does not require resetting existing hypothetical indexes. Real and non-real OIDs can coexist.

Uninstall the extension

DROP EXTENSION hypopg;