Skip to content

Introduce the ability to set index visibility using ALTER INDEX #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions doc/src/sgml/catalogs.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -4618,6 +4618,17 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
partial index.
</para></entry>
</row>

<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>indisvisible</structfield> <type>bool</type>
</para>
<para>
If true, the index is currently visible to the planner and may be used for queries.
If false, the index is invisible and may not be used for queries,
but is still updated when the table is modified. Default is true.
</para></entry>
</row>
</tbody>
</tgroup>
</table>
Expand Down
16 changes: 16 additions & 0 deletions doc/src/sgml/config.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -5814,6 +5814,22 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
</listitem>
</varlistentry>

<varlistentry id="guc-force-invisible-index" xreflabel="use_invisible_index">
<term><varname>use_invisible_index</varname> (<type>boolean</type>)
<indexterm>
<primary><varname>use_invisible_index</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
Forces the query planner to consider indexes that have been marked as invisible using
<command>ALTER INDEX ... INVISIBLE</command>. This parameter is useful for selective
use of invisible indexes in specific application contexts. The default
is <literal>off</literal>.
</para>
</listitem>
</varlistentry>

</variablelist>
</sect2>
<sect2 id="runtime-config-query-constants">
Expand Down
21 changes: 21 additions & 0 deletions doc/src/sgml/indices.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,27 @@ CREATE INDEX test1c_content_y_index ON test1c (content COLLATE "y");
<productname>PostgreSQL</productname> developers to examine the issue.
</para>
</listitem>

<listitem>
<para>
Invisible indexes provide a convenient way to experiment with indexes
without needing to fully drop and recreate them. You can create a new index as
invisible with <command>CREATE INDEX ... INVISIBLE</command> or mark
an existing index invisible with <command>ALTER INDEX ... INVISIBLE</command>.
When an index is invisible, the planner will ignore it by default.
To test the index's effect on performance, set the
<varname>use_invisible_index</varname> parameter to <literal>on</literal>.
This allows you to compare query performance with and without the index
before making it visible to all queries with
<command>ALTER INDEX ... VISIBLE</command>.
</para>
<para>
Similarly, before dropping an existing index that appears unused,
consider marking it invisible to verify that query performance doesn't
degrade. Check <structname>pg_stat_user_indexes</structname>.<structfield>idx_scan</structfield>
to identify potentially unused indexes.
</para>
</listitem>
</itemizedlist>
</sect1>
</chapter>
40 changes: 40 additions & 0 deletions doc/src/sgml/ref/alter_index.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> ALTE
SET STATISTICS <replaceable class="parameter">integer</replaceable>
ALTER INDEX ALL IN TABLESPACE <replaceable class="parameter">name</replaceable> [ OWNED BY <replaceable class="parameter">role_name</replaceable> [, ... ] ]
SET TABLESPACE <replaceable class="parameter">new_tablespace</replaceable> [ NOWAIT ]
ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> { VISIBLE | INVISIBLE }
</synopsis>
</refsynopsisdiv>

Expand Down Expand Up @@ -159,6 +160,31 @@ ALTER INDEX ALL IN TABLESPACE <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>

<varlistentry>
<term><literal>VISIBLE</literal></term>
<listitem>
<para>
Make the specified index visible. The index will be used for query planning.
This operation acquires a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
</para>
</listitem>
</varlistentry>

<varlistentry>
<term><literal>INVISIBLE</literal></term>
<listitem>
<para>
Make the specified index invisible. The index will not be used for queries.
This can be useful for testing query performance with and without specific
indexes. If performance degrades after making an index invisible, it can be easily
be made visible using <literal>VISIBLE</literal>.
</para>
<para>
This operation acquires a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
</para>
</listitem>
</varlistentry>

</variablelist>
</para>

Expand Down Expand Up @@ -301,6 +327,20 @@ CREATE INDEX coord_idx ON measured (x, y, (z + t));
ALTER INDEX coord_idx ALTER COLUMN 3 SET STATISTICS 1000;
</programlisting></para>

<para>
To make an index visible:
<programlisting>
ALTER INDEX idx_name VISIBLE;
</programlisting>
</para>

<para>
To make an index invisible:
<programlisting>
ALTER INDEX idx_name INVISIBLE;
</programlisting>
</para>

</refsect1>

<refsect1>
Expand Down
29 changes: 29 additions & 0 deletions doc/src/sgml/ref/create_index.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
[ WHERE <replaceable class="parameter">predicate</replaceable> ]
[ VISIBLE | INVISIBLE ]
</synopsis>
</refsynopsisdiv>

Expand Down Expand Up @@ -380,6 +381,19 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
</listitem>
</varlistentry>

<varlistentry>
<term><literal>INVISIBLE</literal></term>
<listitem>
<para>
Creates the index in an invisible state (default visible). An invisible index is
not used by the query planner for queries, but it is still maintained
when the underlying table data changes. This can be useful when you want to create
an index without immediately impacting query planning, allowing you to make it
visible later at a more convenient time. The index can be made visible later
using <command>ALTER INDEX ... VISIBLE</command>.
</para>
</listitem>
</varlistentry>
</variablelist>

<refsect2 id="sql-createindex-storage-parameters" xreflabel="Index Storage Parameters">
Expand Down Expand Up @@ -707,6 +721,14 @@ Indexes:
partitioned index is a metadata only operation.
</para>

<para>
When creating an index with the <literal>INVISIBLE</literal> option, the index
will be created but not used for query planning. This can be useful for
preparing an index in advance of its use, or for testing purposes. The index
will still be maintained as the table is modified, so it can be made visible
later without needing to be rebuilt. By default all new indexes are visible.
</para>

</refsect2>
</refsect1>

Expand Down Expand Up @@ -986,6 +1008,13 @@ SELECT * FROM points
CREATE INDEX CONCURRENTLY sales_quantity_index ON sales_table (quantity);
</programlisting></para>

<para>
To create an index on the table <literal>test_table</literal> with the default
name, but have it initially invisible:
<programlisting>
CREATE INDEX ON test_table (col1) INVISIBLE;
</programlisting>
</para>
</refsect1>

<refsect1>
Expand Down
2 changes: 2 additions & 0 deletions src/backend/bootstrap/bootparse.y
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ Boot_DeclareIndexStmt:
stmt->concurrent = false;
stmt->if_not_exists = false;
stmt->reset_default_tblspc = false;
stmt->isvisible = true;

/* locks and races need not concern us in bootstrap mode */
relationId = RangeVarGetRelid(stmt->relation, NoLock,
Expand Down Expand Up @@ -356,6 +357,7 @@ Boot_DeclareUniqueIndexStmt:
stmt->concurrent = false;
stmt->if_not_exists = false;
stmt->reset_default_tblspc = false;
stmt->isvisible = true;

/* locks and races need not concern us in bootstrap mode */
relationId = RangeVarGetRelid(stmt->relation, NoLock,
Expand Down
31 changes: 27 additions & 4 deletions src/backend/catalog/index.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
bool isexclusion,
bool immediate,
bool isvalid,
bool isready);
bool isready,
bool isvisible);
static void index_update_stats(Relation rel,
bool hasindex,
double reltuples);
Expand Down Expand Up @@ -571,7 +572,8 @@ UpdateIndexRelation(Oid indexoid,
bool isexclusion,
bool immediate,
bool isvalid,
bool isready)
bool isready,
bool isvisible)
{
int2vector *indkey;
oidvector *indcollation;
Expand Down Expand Up @@ -649,6 +651,7 @@ UpdateIndexRelation(Oid indexoid,
values[Anum_pg_index_indisready - 1] = BoolGetDatum(isready);
values[Anum_pg_index_indislive - 1] = BoolGetDatum(true);
values[Anum_pg_index_indisreplident - 1] = BoolGetDatum(false);
values[Anum_pg_index_indisvisible - 1] = BoolGetDatum(isvisible);
values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey);
values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation);
values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass);
Expand Down Expand Up @@ -714,6 +717,8 @@ UpdateIndexRelation(Oid indexoid,
* already exists.
* INDEX_CREATE_PARTITIONED:
* create a partitioned index (table must be partitioned)
* INDEX_CREATE_VISIBLE:
* create the index as visible to query planner
* constr_flags: flags passed to index_constraint_create
* (only if INDEX_CREATE_ADD_CONSTRAINT is set)
* allow_system_table_mods: allow table to be a system catalog
Expand Down Expand Up @@ -759,6 +764,7 @@ index_create(Relation heapRelation,
bool invalid = (flags & INDEX_CREATE_INVALID) != 0;
bool concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0;
bool partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0;
bool isvisible = (flags & INDEX_CREATE_VISIBLE) != 0;
char relkind;
TransactionId relfrozenxid;
MultiXactId relminmxid;
Expand Down Expand Up @@ -1042,13 +1048,15 @@ index_create(Relation heapRelation,
* (Or, could define a rule to maintain the predicate) --Nels, Feb '92
* ----------------
*/

UpdateIndexRelation(indexRelationId, heapRelationId, parentIndexRelid,
indexInfo,
collationIds, opclassIds, coloptions,
isprimary, is_exclusion,
(constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0,
!concurrent && !invalid,
!concurrent);
!concurrent,
isvisible);

/*
* Register relcache invalidation on the indexes' heap relation, to
Expand Down Expand Up @@ -1317,6 +1325,8 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
List *indexColNames = NIL;
List *indexExprs = NIL;
List *indexPreds = NIL;
Form_pg_index indexForm;
bits16 createFlags;

indexRelation = index_open(oldIndexId, RowExclusiveLock);

Expand Down Expand Up @@ -1344,6 +1354,9 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
Anum_pg_index_indoption);
indcoloptions = (int2vector *) DatumGetPointer(colOptionDatum);

/* Get the visibility state of the original index */
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);

/* Fetch reloptions of index if any */
classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(oldIndexId));
if (!HeapTupleIsValid(classTuple))
Expand Down Expand Up @@ -1435,6 +1448,16 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
stattargets[i].isnull = isnull;
}

/*
* Determine the create flags for the new index. We always use SKIP_BUILD
* and CONCURRENT for concurrent reindexing. If the original index was
* visible, we also set the VISIBLE flag to maintain the same state in the
* new index.
*/
createFlags = INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT;
if (indexForm->indisvisible)
createFlags |= INDEX_CREATE_VISIBLE;

/*
* Now create the new index.
*
Expand All @@ -1458,7 +1481,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
indcoloptions->values,
stattargets,
reloptionsDatum,
INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT,
createFlags,
0,
true, /* allow table to be a system catalog? */
false, /* is_internal? */
Expand Down
2 changes: 1 addition & 1 deletion src/backend/catalog/toasting.c
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
BTREE_AM_OID,
rel->rd_rel->reltablespace,
collationIds, opclassIds, NULL, coloptions, NULL, (Datum) 0,
INDEX_CREATE_IS_PRIMARY, 0, true, true, NULL);
INDEX_CREATE_IS_PRIMARY | INDEX_CREATE_VISIBLE, 0, true, true, NULL);

table_close(toast_rel, NoLock);

Expand Down
4 changes: 4 additions & 0 deletions src/backend/commands/indexcmds.c
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,10 @@ DefineIndex(Oid tableId,
flags |= INDEX_CREATE_PARTITIONED;
if (stmt->primary)
flags |= INDEX_CREATE_IS_PRIMARY;
if (stmt->isvisible)
flags |= INDEX_CREATE_VISIBLE;
else
flags &= ~INDEX_CREATE_VISIBLE;

/*
* If the table is partitioned, and recursion was declined but partitions
Expand Down
Loading