20130618_2_reindex_concurrently_v27.patch
application/octet-stream
Filename: 20130618_2_reindex_concurrently_v27.patch
Type: application/octet-stream
Part: 1
Message:
Re: Support for REINDEX CONCURRENTLY
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: context
Series: patch v27
| File | + | − |
|---|---|---|
| doc/src/sgml/mvcc.sgml | 3 | 0 |
| doc/src/sgml/ref/reindex.sgml | 156 | 0 |
| src/backend/catalog/index.c | 483 | 0 |
| src/backend/catalog/toasting.c | 1 | 0 |
| src/backend/commands/indexcmds.c | 638 | 0 |
| src/backend/commands/tablecmds.c | 32 | 0 |
| src/backend/executor/execUtils.c | 14 | 0 |
| src/backend/nodes/copyfuncs.c | 1 | 0 |
| src/backend/nodes/equalfuncs.c | 1 | 0 |
| src/backend/parser/gram.y | 9 | 0 |
| src/backend/storage/ipc/procarray.c | 146 | 0 |
| src/backend/tcop/utility.c | 8 | 0 |
| src/include/catalog/index.h | 25 | 0 |
| src/include/commands/defrem.h | 4 | 0 |
| src/include/nodes/parsenodes.h | 1 | 0 |
| src/include/storage/procarray.h | 4 | 0 |
| src/test/regress/expected/create_index.out | 55 | 0 |
| src/test/regress/sql/create_index.sql | 40 | 0 |
*** a/doc/src/sgml/mvcc.sgml
--- b/doc/src/sgml/mvcc.sgml
***************
*** 863,870 **** ERROR: could not serialize access due to read/write dependencies among transact
<para>
Acquired by <command>VACUUM</command> (without <option>FULL</option>),
! <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
! some forms of <command>ALTER TABLE</command>.
</para>
</listitem>
</varlistentry>
--- 863,871 ----
<para>
Acquired by <command>VACUUM</command> (without <option>FULL</option>),
! <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>,
! <command>REINDEX CONCURRENTLY</> and some forms of
! <command>ALTER TABLE</command>.
</para>
</listitem>
</varlistentry>
*** a/doc/src/sgml/ref/reindex.sgml
--- b/doc/src/sgml/ref/reindex.sgml
***************
*** 21,27 **** PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
! REINDEX { INDEX | TABLE | DATABASE | SYSTEM } <replaceable class="PARAMETER">name</replaceable> [ FORCE ]
</synopsis>
</refsynopsisdiv>
--- 21,27 ----
<refsynopsisdiv>
<synopsis>
! REINDEX { INDEX | TABLE | DATABASE | SYSTEM } [ CONCURRENTLY ] <replaceable class="PARAMETER">name</replaceable> [ FORCE ]
</synopsis>
</refsynopsisdiv>
***************
*** 68,76 **** REINDEX { INDEX | TABLE | DATABASE | SYSTEM } <replaceable class="PARAMETER">nam
An index build with the <literal>CONCURRENTLY</> option failed, leaving
an <quote>invalid</> index. Such indexes are useless but it can be
convenient to use <command>REINDEX</> to rebuild them. Note that
! <command>REINDEX</> will not perform a concurrent build. To build the
! index without interfering with production you should drop the index and
! reissue the <command>CREATE INDEX CONCURRENTLY</> command.
</para>
</listitem>
--- 68,88 ----
An index build with the <literal>CONCURRENTLY</> option failed, leaving
an <quote>invalid</> index. Such indexes are useless but it can be
convenient to use <command>REINDEX</> to rebuild them. Note that
! <command>REINDEX</> will perform a concurrent build if <literal>
! CONCURRENTLY</> is specified. To build the index without interfering
! with production you should drop the index and reissue either the
! <command>CREATE INDEX CONCURRENTLY</> or <command>REINDEX CONCURRENTLY</>
! command. Indexes of toast relations can be rebuilt with <command>REINDEX
! CONCURRENTLY</>.
! </para>
! </listitem>
!
! <listitem>
! <para>
! Concurrent indexes based on a <literal>PRIMARY KEY</> or an <literal>
! EXCLUDE</> constraint need to be dropped with <literal>ALTER TABLE
! DROP CONSTRAINT</>. This is also the case of <literal>UNIQUE</> indexes
! using constraints. Other indexes can be dropped using <literal>DROP INDEX</>.
</para>
</listitem>
***************
*** 139,144 **** REINDEX { INDEX | TABLE | DATABASE | SYSTEM } <replaceable class="PARAMETER">nam
--- 151,171 ----
</varlistentry>
<varlistentry>
+ <term><literal>CONCURRENTLY</literal></term>
+ <listitem>
+ <para>
+ When this option is used, <productname>PostgreSQL</> will rebuild the
+ index without taking any locks that prevent concurrent inserts,
+ updates, or deletes on the table; whereas a standard reindex build
+ locks out writes (but not reads) on the table until it's done.
+ There are several caveats to be aware of when using this option
+ — see <xref linkend="SQL-REINDEX-CONCURRENTLY"
+ endterm="SQL-REINDEX-CONCURRENTLY-title">.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>FORCE</literal></term>
<listitem>
<para>
***************
*** 231,236 **** REINDEX { INDEX | TABLE | DATABASE | SYSTEM } <replaceable class="PARAMETER">nam
--- 258,376 ----
to be reindexed by separate commands. This is still possible, but
redundant.
</para>
+
+
+ <refsect2 id="SQL-REINDEX-CONCURRENTLY">
+ <title id="SQL-REINDEX-CONCURRENTLY-title">Rebuilding Indexes Concurrently</title>
+
+ <indexterm zone="SQL-REINDEX-CONCURRENTLY">
+ <primary>index</primary>
+ <secondary>rebuilding concurrently</secondary>
+ </indexterm>
+
+ <para>
+ Rebuilding an index can interfere with regular operation of a database.
+ Normally <productname>PostgreSQL</> locks the table whose index is rebuilt
+ against writes and performs the entire index build with a single scan of the
+ table. Other transactions can still read the table, but if they try to
+ insert, update, or delete rows in the table they will block until the
+ index rebuild is finished. This could have a severe effect if the system is
+ a live production database. Very large tables can take many hours to be
+ indexed, and even for smaller tables, an index rebuild can lock out writers
+ for periods that are unacceptably long for a production system.
+ </para>
+
+ <para>
+ <productname>PostgreSQL</> supports rebuilding indexes without locking
+ out writes. This method is invoked by specifying the
+ <literal>CONCURRENTLY</> option of <command>REINDEX</>.
+ When this option is used, <productname>PostgreSQL</> must perform two
+ scans of the table for each index that needs to be rebuild and in
+ addition it must wait for all existing transactions that could potentially
+ use the index to terminate. This method requires more total work than a
+ standard index rebuild and takes significantly longer to complete as it
+ needs to wait for unfinished transactions that might modify the index.
+ However, since it allows normal operations to continue while the index
+ is rebuilt, this method is useful for rebuilding indexes in a production
+ environment. Of course, the extra CPU, memory and I/O load imposed by
+ the index rebuild might slow other operations.
+ </para>
+
+ <para>
+ In a concurrent index build, a new index whose storage will replace the one
+ to be rebuild is actually entered into the system catalogs in one transaction,
+ then two table scans occur in two more transactions. Once this is performed,
+ the old and fresh indexes are swapped in. During this phase the concurrent
+ index is marked as valid, is then swapped and marked as invalid. An exclusive
+ lock is taken at this phase. Finally two additional transactions are used to
+ mark the concurrent index as not ready and then drop it.
+ </para>
+
+ <para>
+ If a problem arises while rebuilding the indexes, such as a
+ uniqueness violation in a unique index, the <command>REINDEX</>
+ command will fail but leave behind an <quote>invalid</> new index on top
+ of the existing one. This index will be ignored for querying purposes
+ because it might be incomplete; however it will still consume update
+ overhead. The <application>psql</> <command>\d</> command will report
+ such an index as <literal>INVALID</>:
+
+ <programlisting>
+ postgres=# \d tab
+ Table "public.tab"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ col | integer |
+ Indexes:
+ "idx" btree (col)
+ "idx_cct" btree (col) INVALID
+ </programlisting>
+
+ The recommended recovery method in such cases is to drop the concurrent
+ index and try again to perform <command>REINDEX CONCURRENTLY</>.
+ The concurrent index created during the processing has a name finishing by
+ the suffix cct. This works as well with indexes of toast relations.
+ </para>
+
+ <para>
+ Regular index builds permit other regular index builds on the
+ same table to occur in parallel, but only one concurrent index build
+ can occur on a table at a time. In both cases, no other types of schema
+ modification on the table are allowed meanwhile. Another difference
+ is that a regular <command>REINDEX TABLE</> or <command>REINDEX INDEX</>
+ command can be performed within a transaction block, but
+ <command>REINDEX CONCURRENTLY</> cannot. <command>REINDEX DATABASE</> is
+ by default not allowed to run inside a transaction block, so in this case
+ <command>CONCURRENTLY</> is not supported.
+ </para>
+
+ <para>
+ Invalid indexes of toast relations can be dropped if a failure occurred
+ during <command>REINDEX CONCURRENTLY</>. Live indexes of toast relations
+ cannot be dropped.
+ </para>
+
+ <para>
+ <command>REINDEX DATABASE</command> used with <command>CONCURRENTLY
+ </command> rebuilds concurrently only the non-system relations. System
+ relations are rebuilt with a non-concurrent context. Toast indexes are
+ rebuilt concurrently if the relation they depend on is a non-system
+ relation.
+ </para>
+
+ <para>
+ <command>REINDEX</command> uses <literal>ACCESS EXCLUSIVE</literal> lock
+ on all the relations involved during operation. When <command>CONCURRENTLY</command>
+ is specified, the operation is done with <literal>SHARE UPDATE EXCLUSIVE</literal>
+ except during relation swap where <literal>ACCESS EXCLUSIVE</literal> lock
+ is taken.
+ </para>
+
+ <para>
+ <command>REINDEX SYSTEM</command> does not support <command>CONCURRENTLY
+ </command>.
+ </para>
+ </refsect2>
</refsect1>
<refsect1>
***************
*** 262,268 **** $ <userinput>psql broken_db</userinput>
...
broken_db=> REINDEX DATABASE broken_db;
broken_db=> \q
! </programlisting></para>
</refsect1>
<refsect1>
--- 402,419 ----
...
broken_db=> REINDEX DATABASE broken_db;
broken_db=> \q
! </programlisting>
! </para>
!
! <para>
! Rebuild a table while authorizing read and write operations on involved
! relations when performed:
!
! <programlisting>
! REINDEX TABLE CONCURRENTLY my_broken_table;
! </programlisting>
! </para>
!
</refsect1>
<refsect1>
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 43,51 ****
--- 43,53 ----
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
+ #include "commands/defrem.h"
#include "commands/tablecmds.h"
#include "commands/trigger.h"
#include "executor/executor.h"
+ #include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
***************
*** 672,677 **** UpdateIndexRelation(Oid indexoid,
--- 674,683 ----
* will be marked "invalid" and the caller must take additional steps
* to fix it up.
* is_internal: if true, post creation hook for new index
+ * is_reindex: if true, create an index that is used as a duplicate of an
+ * existing index created during a concurrent operation. This index can
+ * also be a toast relation. Sufficient locks are normally taken on
+ * the related relations once this is called during a concurrent operation.
*
* Returns the OID of the created index.
*/
***************
*** 695,701 **** index_create(Relation heapRelation,
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
! bool is_internal)
{
Oid heapRelationId = RelationGetRelid(heapRelation);
Relation pg_class;
--- 701,708 ----
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
! bool is_internal,
! bool is_reindex)
{
Oid heapRelationId = RelationGetRelid(heapRelation);
Relation pg_class;
***************
*** 738,756 **** index_create(Relation heapRelation,
/*
* concurrent index build on a system catalog is unsafe because we tend to
! * release locks before committing in catalogs
*/
if (concurrent &&
! IsSystemRelation(heapRelation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("concurrent index creation on system catalog tables is not supported")));
/*
! * This case is currently not supported, but there's no way to ask for it
! * in the grammar anyway, so it can't happen.
*/
! if (concurrent && is_exclusion)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg_internal("concurrent index creation for exclusion constraints is not supported")));
--- 745,766 ----
/*
* concurrent index build on a system catalog is unsafe because we tend to
! * release locks before committing in catalogs. If the index is created during
! * a REINDEX CONCURRENTLY operation, sufficient locks are already taken.
*/
if (concurrent &&
! IsSystemRelation(heapRelation) &&
! !is_reindex)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("concurrent index creation on system catalog tables is not supported")));
/*
! * This case is currently only supported during a concurrent index
! * rebuild, but there is no way to ask for it in the grammar otherwise
! * anyway.
*/
! if (concurrent && is_exclusion && !is_reindex)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg_internal("concurrent index creation for exclusion constraints is not supported")));
***************
*** 1090,1095 **** index_create(Relation heapRelation,
--- 1100,1537 ----
return indexRelationId;
}
+
+ /*
+ * index_concurrent_create
+ *
+ * Create an index based on the given one that will be used for concurrent
+ * operations. The index is inserted into catalogs and needs to be built later
+ * on. This is called during concurrent index processing. The heap relation
+ * on which is based the index needs to be closed by the caller.
+ */
+ Oid
+ index_concurrent_create(Relation heapRelation, Oid indOid, char *concurrentName)
+ {
+ Relation indexRelation;
+ IndexInfo *indexInfo;
+ Oid concurrentOid = InvalidOid;
+ List *columnNames = NIL;
+ List *indexprs = NIL;
+ ListCell *indexpr_item;
+ int i;
+ HeapTuple indexTuple, classTuple;
+ Datum indclassDatum, colOptionDatum, optionDatum;
+ oidvector *indclass;
+ int2vector *indcoloptions;
+ bool isnull;
+ bool initdeferred = false;
+ Oid constraintOid = get_index_constraint(indOid);
+
+ indexRelation = index_open(indOid, RowExclusiveLock);
+
+ /* Concurrent index uses the same index information as former index */
+ indexInfo = BuildIndexInfo(indexRelation);
+
+ /*
+ * Determine if index is initdeferred, this depends on its dependent
+ * constraint.
+ */
+ if (OidIsValid(constraintOid))
+ {
+ /* Look for the correct value */
+ HeapTuple constraintTuple;
+ Form_pg_constraint constraintForm;
+
+ constraintTuple = SearchSysCache1(CONSTROID,
+ ObjectIdGetDatum(constraintOid));
+ if (!HeapTupleIsValid(constraintTuple))
+ elog(ERROR, "cache lookup failed for constraint %u",
+ constraintOid);
+ constraintForm = (Form_pg_constraint) GETSTRUCT(constraintTuple);
+ initdeferred = constraintForm->condeferred;
+
+ ReleaseSysCache(constraintTuple);
+ }
+
+ /* Get expressions associated to this index for compilation of column names */
+ indexprs = RelationGetIndexExpressions(indexRelation);
+ indexpr_item = list_head(indexprs);
+
+ /* Build the list of column names, necessary for index_create */
+ for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+ {
+ char *origname, *curname;
+ char buf[NAMEDATALEN];
+ AttrNumber attnum = indexInfo->ii_KeyAttrNumbers[i];
+ int j;
+
+ /* Pick up column name depending on attribute type */
+ if (attnum > 0)
+ {
+ /*
+ * This is a column attribute, so simply pick column name from
+ * relation.
+ */
+ Form_pg_attribute attform = heapRelation->rd_att->attrs[attnum - 1];;
+ origname = pstrdup(NameStr(attform->attname));
+ }
+ else if (attnum < 0)
+ {
+ /* Case of a system attribute */
+ Form_pg_attribute attform = SystemAttributeDefinition(attnum,
+ heapRelation->rd_rel->relhasoids);
+ origname = pstrdup(NameStr(attform->attname));
+ }
+ else
+ {
+ Node *indnode;
+ /*
+ * This is the case of an expression, so pick up the expression
+ * name.
+ */
+ Assert(indexpr_item != NULL);
+ indnode = (Node *) lfirst(indexpr_item);
+ indexpr_item = lnext(indexpr_item);
+ origname = deparse_expression(indnode,
+ deparse_context_for(RelationGetRelationName(heapRelation),
+ RelationGetRelid(heapRelation)),
+ false, false);
+ }
+
+ /*
+ * Check if the name picked has any conflict with existing names and
+ * change it.
+ */
+ curname = origname;
+ for (j = 1;; j++)
+ {
+ ListCell *lc2;
+ char nbuf[32];
+ int nlen;
+
+ foreach(lc2, columnNames)
+ {
+ if (strcmp(curname, (char *) lfirst(lc2)) == 0)
+ break;
+ }
+ if (lc2 == NULL)
+ break; /* found nonconflicting name */
+
+ sprintf(nbuf, "%d", j);
+
+ /* Ensure generated names are shorter than NAMEDATALEN */
+ nlen = pg_mbcliplen(origname, strlen(origname),
+ NAMEDATALEN - 1 - strlen(nbuf));
+ memcpy(buf, origname, nlen);
+ strcpy(buf + nlen, nbuf);
+ curname = buf;
+ }
+
+ /* Append name to existing list */
+ columnNames = lappend(columnNames, pstrdup(curname));
+ }
+
+ /* Get the array of class and column options IDs from index info */
+ indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indOid));
+ if (!HeapTupleIsValid(indexTuple))
+ elog(ERROR, "cache lookup failed for index %u", indOid);
+ indclassDatum = SysCacheGetAttr(INDEXRELID, indexTuple,
+ Anum_pg_index_indclass, &isnull);
+ Assert(!isnull);
+ indclass = (oidvector *) DatumGetPointer(indclassDatum);
+
+ colOptionDatum = SysCacheGetAttr(INDEXRELID, indexTuple,
+ Anum_pg_index_indoption, &isnull);
+ Assert(!isnull);
+ indcoloptions = (int2vector *) DatumGetPointer(colOptionDatum);
+
+ /* Fetch options of index if any */
+ classTuple = SearchSysCache1(RELOID, indOid);
+ if (!HeapTupleIsValid(classTuple))
+ elog(ERROR, "cache lookup failed for relation %u", indOid);
+ optionDatum = SysCacheGetAttr(RELOID, classTuple,
+ Anum_pg_class_reloptions, &isnull);
+
+ /* Now create the concurrent index */
+ concurrentOid = index_create(heapRelation,
+ (const char *) concurrentName,
+ InvalidOid,
+ InvalidOid,
+ indexInfo,
+ columnNames,
+ indexRelation->rd_rel->relam,
+ indexRelation->rd_rel->reltablespace,
+ indexRelation->rd_indcollation,
+ indclass->values,
+ indcoloptions->values,
+ optionDatum,
+ indexRelation->rd_index->indisprimary,
+ OidIsValid(constraintOid), /* is constraint? */
+ !indexRelation->rd_index->indimmediate, /* is deferrable? */
+ initdeferred, /* is initially deferred? */
+ true, /* allow table to be a system catalog? */
+ true, /* skip build? */
+ true, /* concurrent? */
+ false, /* is_internal */
+ true); /* reindex? */
+
+ /* Close the relations used and clean up */
+ index_close(indexRelation, NoLock);
+ ReleaseSysCache(indexTuple);
+ ReleaseSysCache(classTuple);
+
+ return concurrentOid;
+ }
+
+
+ /*
+ * index_concurrent_build
+ *
+ * Build index for a concurrent operation. Low-level locks are taken when this
+ * operation is performed to prevent only schema changes.
+ */
+ void
+ index_concurrent_build(Oid heapOid,
+ Oid indexOid,
+ bool isprimary)
+ {
+ Relation rel,
+ indexRelation;
+ IndexInfo *indexInfo;
+
+ /* Open and lock the parent heap relation */
+ rel = heap_open(heapOid, ShareUpdateExclusiveLock);
+
+ /* And the target index relation */
+ indexRelation = index_open(indexOid, RowExclusiveLock);
+
+ /*
+ * We have to re-build the IndexInfo struct, since it was lost in
+ * commit of transaction where this concurrent index was created
+ * at the catalog level.
+ */
+ indexInfo = BuildIndexInfo(indexRelation);
+ Assert(!indexInfo->ii_ReadyForInserts);
+ indexInfo->ii_Concurrent = true;
+ indexInfo->ii_BrokenHotChain = false;
+
+ /* Now build the index */
+ index_build(rel, indexRelation, indexInfo, isprimary, false);
+
+ /* Close both the relations, but keep the locks */
+ heap_close(rel, NoLock);
+ index_close(indexRelation, NoLock);
+ }
+
+
+ /*
+ * index_concurrent_swap
+ *
+ * Swap old index and new index in a concurrent context. For the time being
+ * what is done here is switching the relation relfilenode of the indexes. If
+ * extra operations are necessary during a concurrent swap, processing should
+ * be added here. AccessExclusiveLock is taken on the index relations that are
+ * swapped until the end of the transaction where this function is called.
+ * Note: a lower lock could be taken if catalog cache with SnapshotNow was
+ * correctly MVCC'd.
+ */
+ void
+ index_concurrent_swap(Oid newIndexOid, Oid oldIndexOid)
+ {
+ Relation oldIndexRel, newIndexRel, pg_class;
+ HeapTuple oldIndexTuple, newIndexTuple;
+ Form_pg_class oldIndexForm, newIndexForm;
+ Oid tmpnode;
+
+ /*
+ * Take an exclusive lock on the old and new index before swapping them.
+ */
+ oldIndexRel = relation_open(oldIndexOid, AccessExclusiveLock);
+ newIndexRel = relation_open(newIndexOid, AccessExclusiveLock);
+
+ /* Now swap relfilenode of those indexes */
+ pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+
+ oldIndexTuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(oldIndexOid));
+ if (!HeapTupleIsValid(oldIndexTuple))
+ elog(ERROR, "could not find tuple for relation %u", oldIndexOid);
+ newIndexTuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(newIndexOid));
+ if (!HeapTupleIsValid(newIndexTuple))
+ elog(ERROR, "could not find tuple for relation %u", newIndexOid);
+ oldIndexForm = (Form_pg_class) GETSTRUCT(oldIndexTuple);
+ newIndexForm = (Form_pg_class) GETSTRUCT(newIndexTuple);
+
+ /* Here is where the actual swapping happens */
+ tmpnode = oldIndexForm->relfilenode;
+ oldIndexForm->relfilenode = newIndexForm->relfilenode;
+ newIndexForm->relfilenode = tmpnode;
+
+ /* Then update the tuples for each relation */
+ simple_heap_update(pg_class, &oldIndexTuple->t_self, oldIndexTuple);
+ simple_heap_update(pg_class, &newIndexTuple->t_self, newIndexTuple);
+ CatalogUpdateIndexes(pg_class, oldIndexTuple);
+ CatalogUpdateIndexes(pg_class, newIndexTuple);
+
+ /* Close relations and clean up */
+ heap_freetuple(oldIndexTuple);
+ heap_freetuple(newIndexTuple);
+ heap_close(pg_class, RowExclusiveLock);
+
+ /* The lock taken previously is not released until the end of transaction */
+ relation_close(oldIndexRel, NoLock);
+ relation_close(newIndexRel, NoLock);
+ }
+
+ /*
+ * index_concurrent_set_dead
+ *
+ * Perform the last invalidation stage of DROP INDEX CONCURRENTLY before
+ * actually dropping the index. After calling this function the index is
+ * seen by all the backends as dead.
+ */
+ void
+ index_concurrent_set_dead(Oid indexId, Oid heapId, LOCKTAG locktag)
+ {
+ Relation heapRelation;
+ Relation indexRelation;
+
+ /*
+ * Now we must wait until no running transaction could be using the
+ * index for a query if necessary.
+ *
+ * Note: the reason we use actual lock acquisition here, rather than
+ * just checking the ProcArray and sleeping, is that deadlock is
+ * possible if one of the transactions in question is blocked trying
+ * to acquire an exclusive lock on our table. The lock code will
+ * detect deadlock and error out properly.
+ */
+ WaitForVirtualLocks(locktag, AccessExclusiveLock);
+
+ /*
+ * No more predicate locks will be acquired on this index, and we're
+ * about to stop doing inserts into the index which could show
+ * conflicts with existing predicate locks, so now is the time to move
+ * them to the heap relation.
+ */
+ heapRelation = heap_open(heapId, ShareUpdateExclusiveLock);
+ indexRelation = index_open(indexId, ShareUpdateExclusiveLock);
+ TransferPredicateLocksToHeapRelation(indexRelation);
+
+ /*
+ * Now we are sure that nobody uses the index for queries; they just
+ * might have it open for updating it. So now we can unset indisready
+ * and indislive, then wait till nobody could be using it at all
+ * anymore.
+ */
+ index_set_state_flags(indexId, INDEX_DROP_SET_DEAD, true);
+
+ /*
+ * Invalidate the relcache for the table, so that after this commit
+ * all sessions will refresh the table's index list. Forgetting just
+ * the index's relcache entry is not enough.
+ */
+ CacheInvalidateRelcache(heapRelation);
+
+ /*
+ * Close the relations again, though still holding session lock.
+ */
+ heap_close(heapRelation, NoLock);
+ index_close(indexRelation, NoLock);
+ }
+
+ /*
+ * index_concurrent_clear_valid
+ *
+ * Release the valid state of a given index and then release the cache of
+ * its parent relation. This function should be called when initializing an
+ * index drop in a concurrent context before setting the index as dead if
+ * if called in a concurrent context.
+ */
+ void
+ index_concurrent_clear_valid(Relation heapRelation,
+ Oid indexOid,
+ bool concurrent)
+ {
+ /*
+ * Mark index invalid by updating its pg_index entry
+ */
+ index_set_state_flags(indexOid, INDEX_DROP_CLEAR_VALID, concurrent);
+
+ /*
+ * Invalidate the relcache for the table, so that after this commit
+ * all sessions will refresh any cached plans that might reference the
+ * index.
+ */
+ CacheInvalidateRelcache(heapRelation);
+ }
+
+ /*
+ * index_concurrent_drop
+ *
+ * Drop a single index concurrently as the last step of an index concurrent
+ * process. Deletion is done through performDeletion or dependencies of the
+ * index would not get dropped. At this point all the indexes are already
+ * considered as invalid and dead so they can be dropped without using any
+ * concurrent options as it is sure that they will not interact with other
+ * server sessions.
+ */
+ void
+ index_concurrent_drop(Oid indexOid)
+ {
+ Oid constraintOid = get_index_constraint(indexOid);
+ ObjectAddress object;
+ Form_pg_index indexForm;
+ Relation pg_index;
+ HeapTuple indexTuple;
+
+ /*
+ * Check that the index dropped here is not alive, it might be used by
+ * other backends in this case.
+ */
+ pg_index = heap_open(IndexRelationId, RowExclusiveLock);
+
+ indexTuple = SearchSysCacheCopy1(INDEXRELID,
+ ObjectIdGetDatum(indexOid));
+ if (!HeapTupleIsValid(indexTuple))
+ elog(ERROR, "cache lookup failed for index %u", indexOid);
+ indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
+
+ /*
+ * This is only a safety check, just to avoid live indexes from being
+ * dropped.
+ */
+ if (indexForm->indislive)
+ elog(ERROR, "cannot drop live index with OID %u", indexOid);
+
+ /* Clean up */
+ heap_close(pg_index, RowExclusiveLock);
+
+ /*
+ * We are sure to have a dead index, so begin the drop process.
+ * Register constraint or index for drop.
+ */
+ if (OidIsValid(constraintOid))
+ {
+ object.classId = ConstraintRelationId;
+ object.objectId = constraintOid;
+ }
+ else
+ {
+ object.classId = RelationRelationId;
+ object.objectId = indexOid;
+ }
+
+ object.objectSubId = 0;
+
+ /* Perform deletion for normal and toast indexes */
+ performDeletion(&object,
+ DROP_RESTRICT,
+ 0);
+ }
+
+
/*
* index_constraint_create
*
***************
*** 1325,1331 **** index_drop(Oid indexId, bool concurrent)
indexrelid;
LOCKTAG heaplocktag;
LOCKMODE lockmode;
- VirtualTransactionId *old_lockholders;
/*
* To drop an index safely, we must grab exclusive lock on its parent
--- 1767,1772 ----
***************
*** 1407,1423 **** index_drop(Oid indexId, bool concurrent)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DROP INDEX CONCURRENTLY must be first action in transaction")));
! /*
! * Mark index invalid by updating its pg_index entry
! */
! index_set_state_flags(indexId, INDEX_DROP_CLEAR_VALID);
!
! /*
! * Invalidate the relcache for the table, so that after this commit
! * all sessions will refresh any cached plans that might reference the
! * index.
! */
! CacheInvalidateRelcache(userHeapRelation);
/* save lockrelid and locktag for below, then close but keep locks */
heaprelid = userHeapRelation->rd_lockInfo.lockRelId;
--- 1848,1855 ----
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DROP INDEX CONCURRENTLY must be first action in transaction")));
! /* Mark the index as invalid */
! index_concurrent_clear_valid(userHeapRelation, indexId, true);
/* save lockrelid and locktag for below, then close but keep locks */
heaprelid = userHeapRelation->rd_lockInfo.lockRelId;
***************
*** 1445,1507 **** index_drop(Oid indexId, bool concurrent)
CommitTransactionCommand();
StartTransactionCommand();
! /*
! * Now we must wait until no running transaction could be using the
! * index for a query. To do this, inquire which xacts currently would
! * conflict with AccessExclusiveLock on the table -- ie, which ones
! * have a lock of any kind on the table. Then wait for each of these
! * xacts to commit or abort. Note we do not need to worry about xacts
! * that open the table for reading after this point; they will see the
! * index as invalid when they open the relation.
! *
! * Note: the reason we use actual lock acquisition here, rather than
! * just checking the ProcArray and sleeping, is that deadlock is
! * possible if one of the transactions in question is blocked trying
! * to acquire an exclusive lock on our table. The lock code will
! * detect deadlock and error out properly.
! *
! * Note: GetLockConflicts() never reports our own xid, hence we need
! * not check for that. Also, prepared xacts are not reported, which
! * is fine since they certainly aren't going to do anything more.
! */
! old_lockholders = GetLockConflicts(&heaplocktag, AccessExclusiveLock);
!
! while (VirtualTransactionIdIsValid(*old_lockholders))
! {
! VirtualXactLock(*old_lockholders, true);
! old_lockholders++;
! }
!
! /*
! * No more predicate locks will be acquired on this index, and we're
! * about to stop doing inserts into the index which could show
! * conflicts with existing predicate locks, so now is the time to move
! * them to the heap relation.
! */
! userHeapRelation = heap_open(heapId, ShareUpdateExclusiveLock);
! userIndexRelation = index_open(indexId, ShareUpdateExclusiveLock);
! TransferPredicateLocksToHeapRelation(userIndexRelation);
!
! /*
! * Now we are sure that nobody uses the index for queries; they just
! * might have it open for updating it. So now we can unset indisready
! * and indislive, then wait till nobody could be using it at all
! * anymore.
! */
! index_set_state_flags(indexId, INDEX_DROP_SET_DEAD);
!
! /*
! * Invalidate the relcache for the table, so that after this commit
! * all sessions will refresh the table's index list. Forgetting just
! * the index's relcache entry is not enough.
! */
! CacheInvalidateRelcache(userHeapRelation);
!
! /*
! * Close the relations again, though still holding session lock.
! */
! heap_close(userHeapRelation, NoLock);
! index_close(userIndexRelation, NoLock);
/*
* Again, commit the transaction to make the pg_index update visible
--- 1877,1884 ----
CommitTransactionCommand();
StartTransactionCommand();
! /* Finish invalidation of index and mark it as dead */
! index_concurrent_set_dead(indexId, heapId, heaplocktag);
/*
* Again, commit the transaction to make the pg_index update visible
***************
*** 1514,1526 **** index_drop(Oid indexId, bool concurrent)
* Wait till every transaction that saw the old index state has
* finished. The logic here is the same as above.
*/
! old_lockholders = GetLockConflicts(&heaplocktag, AccessExclusiveLock);
!
! while (VirtualTransactionIdIsValid(*old_lockholders))
! {
! VirtualXactLock(*old_lockholders, true);
! old_lockholders++;
! }
/*
* Re-open relations to allow us to complete our actions.
--- 1891,1897 ----
* Wait till every transaction that saw the old index state has
* finished. The logic here is the same as above.
*/
! WaitForVirtualLocks(heaplocktag, AccessExclusiveLock);
/*
* Re-open relations to allow us to complete our actions.
***************
*** 2991,3017 **** validate_index_heapscan(Relation heapRelation,
* index_set_state_flags - adjust pg_index state flags
*
* This is used during CREATE/DROP INDEX CONCURRENTLY to adjust the pg_index
! * flags that denote the index's state. We must use an in-place update of
! * the pg_index tuple, because we do not have exclusive lock on the parent
! * table and so other sessions might concurrently be doing SnapshotNow scans
! * of pg_index to identify the table's indexes. A transactional update would
! * risk somebody not seeing the index at all. Because the update is not
! * transactional and will not roll back on error, this must only be used as
! * the last step in a transaction that has not made any transactional catalog
! * updates!
*
* Note that heap_inplace_update does send a cache inval message for the
* tuple, so other sessions will hear about the update as soon as we commit.
*/
void
! index_set_state_flags(Oid indexId, IndexStateFlagsAction action)
{
Relation pg_index;
HeapTuple indexTuple;
Form_pg_index indexForm;
! /* Assert that current xact hasn't done any transactional updates */
! Assert(GetTopTransactionIdIfAny() == InvalidTransactionId);
/* Open pg_index and fetch a writable copy of the index's tuple */
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
--- 3362,3393 ----
* index_set_state_flags - adjust pg_index state flags
*
* This is used during CREATE/DROP INDEX CONCURRENTLY to adjust the pg_index
! * flags that denote the index's state. If this function is called in a
! * concurrent process, we use an in-place update of the pg_index tuple,
! * because we do not have exclusive lock on the parent table and so other
! * sessions might concurrently be doing SnapshotNow scans of pg_index to
! * identify the table's indexes. A transactional update would risk somebody
! * not seeing the index at all. Because the update is not transactional
! * and will not roll back on error, this must only be used as the last step
! * in a transaction that has not made any transactional catalog updates!
*
* Note that heap_inplace_update does send a cache inval message for the
* tuple, so other sessions will hear about the update as soon as we commit.
*/
void
! index_set_state_flags(Oid indexId,
! IndexStateFlagsAction action,
! bool concurrent)
{
Relation pg_index;
HeapTuple indexTuple;
Form_pg_index indexForm;
! /*
! * Assert that current xact hasn't done any transactional updates, there
! * is nothing to worry in a non-concurrent context.
! */
! Assert(!concurrent || GetTopTransactionIdIfAny() == InvalidTransactionId);
/* Open pg_index and fetch a writable copy of the index's tuple */
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
***************
*** 3071,3078 **** index_set_state_flags(Oid indexId, IndexStateFlagsAction action)
break;
}
! /* ... and write it back in-place */
! heap_inplace_update(pg_index, indexTuple);
heap_close(pg_index, RowExclusiveLock);
}
--- 3447,3466 ----
break;
}
! /*
! * Write it back in-place in a concurrent context, and do a simple update
! * for a non-concurrent context.
! */
! if (concurrent)
! {
! heap_inplace_update(pg_index, indexTuple);
! }
! else
! {
! simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
! CommandCounterIncrement();
! CatalogUpdateIndexes(pg_index, indexTuple);
! }
heap_close(pg_index, RowExclusiveLock);
}
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 281,287 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio
rel->rd_rel->reltablespace,
collationObjectId, classObjectId, coloptions, (Datum) 0,
true, false, false, false,
! true, false, false, true);
heap_close(toast_rel, NoLock);
--- 281,287 ----
rel->rd_rel->reltablespace,
collationObjectId, classObjectId, coloptions, (Datum) 0,
true, false, false, false,
! true, false, false, false, false);
heap_close(toast_rel, NoLock);
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 68,75 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
static Oid GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
! List *colnames, List *exclusionOpNames,
! bool primary, bool isconstraint);
static char *ChooseIndexNameAddition(List *colnames);
static List *ChooseIndexColumnNames(List *indexElems);
static void RangeVarCallbackForReindexIndex(const RangeVar *relation,
--- 68,76 ----
static Oid GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
! List *colnames, List *exclusionOpNames,
! bool primary, bool isconstraint,
! bool concurrent);
static char *ChooseIndexNameAddition(List *colnames);
static List *ChooseIndexColumnNames(List *indexElems);
static void RangeVarCallbackForReindexIndex(const RangeVar *relation,
***************
*** 311,317 **** DefineIndex(IndexStmt *stmt,
Oid tablespaceId;
List *indexColNames;
Relation rel;
- Relation indexRelation;
HeapTuple tuple;
Form_pg_am accessMethodForm;
bool amcanorder;
--- 312,317 ----
***************
*** 321,333 **** DefineIndex(IndexStmt *stmt,
IndexInfo *indexInfo;
int numberOfAttributes;
TransactionId limitXmin;
- VirtualTransactionId *old_lockholders;
- VirtualTransactionId *old_snapshots;
- int n_old_snapshots;
LockRelId heaprelid;
LOCKTAG heaplocktag;
Snapshot snapshot;
- int i;
/*
* count attributes in index
--- 321,329 ----
***************
*** 454,460 **** DefineIndex(IndexStmt *stmt,
indexColNames,
stmt->excludeOpNames,
stmt->primary,
! stmt->isconstraint);
/*
* look up the access method, verify it can handle the requested features
--- 450,457 ----
indexColNames,
stmt->excludeOpNames,
stmt->primary,
! stmt->isconstraint,
! false);
/*
* look up the access method, verify it can handle the requested features
***************
*** 601,607 **** DefineIndex(IndexStmt *stmt,
stmt->isconstraint, stmt->deferrable, stmt->initdeferred,
allowSystemTableMods,
skip_build || stmt->concurrent,
! stmt->concurrent, !check_rights);
/* Add any requested comment */
if (stmt->idxcomment != NULL)
--- 598,604 ----
stmt->isconstraint, stmt->deferrable, stmt->initdeferred,
allowSystemTableMods,
skip_build || stmt->concurrent,
! stmt->concurrent, !check_rights, false);
/* Add any requested comment */
if (stmt->idxcomment != NULL)
***************
*** 664,681 **** DefineIndex(IndexStmt *stmt,
* one of the transactions in question is blocked trying to acquire an
* exclusive lock on our table. The lock code will detect deadlock and
* error out properly.
- *
- * Note: GetLockConflicts() never reports our own xid, hence we need not
- * check for that. Also, prepared xacts are not reported, which is fine
- * since they certainly aren't going to do anything more.
*/
! old_lockholders = GetLockConflicts(&heaplocktag, ShareLock);
!
! while (VirtualTransactionIdIsValid(*old_lockholders))
! {
! VirtualXactLock(*old_lockholders, true);
! old_lockholders++;
! }
/*
* At this moment we are sure that there are no transactions with the
--- 661,668 ----
* one of the transactions in question is blocked trying to acquire an
* exclusive lock on our table. The lock code will detect deadlock and
* error out properly.
*/
! WaitForVirtualLocks(heaplocktag, ShareLock);
/*
* At this moment we are sure that there are no transactions with the
***************
*** 695,728 **** DefineIndex(IndexStmt *stmt,
* HOT-chain or the extension of the chain is HOT-safe for this index.
*/
- /* Open and lock the parent heap relation */
- rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
-
- /* And the target index relation */
- indexRelation = index_open(indexRelationId, RowExclusiveLock);
-
/* Set ActiveSnapshot since functions in the indexes may need it */
PushActiveSnapshot(GetTransactionSnapshot());
! /* We have to re-build the IndexInfo struct, since it was lost in commit */
! indexInfo = BuildIndexInfo(indexRelation);
! Assert(!indexInfo->ii_ReadyForInserts);
! indexInfo->ii_Concurrent = true;
! indexInfo->ii_BrokenHotChain = false;
!
! /* Now build the index */
! index_build(rel, indexRelation, indexInfo, stmt->primary, false);
!
! /* Close both the relations, but keep the locks */
! heap_close(rel, NoLock);
! index_close(indexRelation, NoLock);
/*
* Update the pg_index row to mark the index as ready for inserts. Once we
* commit this transaction, any new transactions that open the table must
* insert new entries into the index for insertions and non-HOT updates.
*/
! index_set_state_flags(indexRelationId, INDEX_CREATE_SET_READY);
/* we can do away with our snapshot */
PopActiveSnapshot();
--- 682,701 ----
* HOT-chain or the extension of the chain is HOT-safe for this index.
*/
/* Set ActiveSnapshot since functions in the indexes may need it */
PushActiveSnapshot(GetTransactionSnapshot());
! /* Perform concurrent build of index */
! index_concurrent_build(RangeVarGetRelid(stmt->relation, NoLock, false),
! indexRelationId,
! stmt->primary);
/*
* Update the pg_index row to mark the index as ready for inserts. Once we
* commit this transaction, any new transactions that open the table must
* insert new entries into the index for insertions and non-HOT updates.
*/
! index_set_state_flags(indexRelationId, INDEX_CREATE_SET_READY, true);
/* we can do away with our snapshot */
PopActiveSnapshot();
***************
*** 739,751 **** DefineIndex(IndexStmt *stmt,
* We once again wait until no transaction can have the table open with
* the index marked as read-only for updates.
*/
! old_lockholders = GetLockConflicts(&heaplocktag, ShareLock);
!
! while (VirtualTransactionIdIsValid(*old_lockholders))
! {
! VirtualXactLock(*old_lockholders, true);
! old_lockholders++;
! }
/*
* Now take the "reference snapshot" that will be used by validate_index()
--- 712,718 ----
* We once again wait until no transaction can have the table open with
* the index marked as read-only for updates.
*/
! WaitForVirtualLocks(heaplocktag, ShareLock);
/*
* Now take the "reference snapshot" that will be used by validate_index()
***************
*** 786,864 **** DefineIndex(IndexStmt *stmt,
* The index is now valid in the sense that it contains all currently
* interesting tuples. But since it might not contain tuples deleted just
* before the reference snap was taken, we have to wait out any
! * transactions that might have older snapshots. Obtain a list of VXIDs
! * of such transactions, and wait for them individually.
! *
! * We can exclude any running transactions that have xmin > the xmin of
! * our reference snapshot; their oldest snapshot must be newer than ours.
! * We can also exclude any transactions that have xmin = zero, since they
! * evidently have no live snapshot at all (and any one they might be in
! * process of taking is certainly newer than ours). Transactions in other
! * DBs can be ignored too, since they'll never even be able to see this
! * index.
! *
! * We can also exclude autovacuum processes and processes running manual
! * lazy VACUUMs, because they won't be fazed by missing index entries
! * either. (Manual ANALYZEs, however, can't be excluded because they
! * might be within transactions that are going to do arbitrary operations
! * later.)
! *
! * Also, GetCurrentVirtualXIDs never reports our own vxid, so we need not
! * check for that.
! *
! * If a process goes idle-in-transaction with xmin zero, we do not need to
! * wait for it anymore, per the above argument. We do not have the
! * infrastructure right now to stop waiting if that happens, but we can at
! * least avoid the folly of waiting when it is idle at the time we would
! * begin to wait. We do this by repeatedly rechecking the output of
! * GetCurrentVirtualXIDs. If, during any iteration, a particular vxid
! * doesn't show up in the output, we know we can forget about it.
*/
! old_snapshots = GetCurrentVirtualXIDs(limitXmin, true, false,
! PROC_IS_AUTOVACUUM | PROC_IN_VACUUM,
! &n_old_snapshots);
!
! for (i = 0; i < n_old_snapshots; i++)
! {
! if (!VirtualTransactionIdIsValid(old_snapshots[i]))
! continue; /* found uninteresting in previous cycle */
!
! if (i > 0)
! {
! /* see if anything's changed ... */
! VirtualTransactionId *newer_snapshots;
! int n_newer_snapshots;
! int j;
! int k;
!
! newer_snapshots = GetCurrentVirtualXIDs(limitXmin,
! true, false,
! PROC_IS_AUTOVACUUM | PROC_IN_VACUUM,
! &n_newer_snapshots);
! for (j = i; j < n_old_snapshots; j++)
! {
! if (!VirtualTransactionIdIsValid(old_snapshots[j]))
! continue; /* found uninteresting in previous cycle */
! for (k = 0; k < n_newer_snapshots; k++)
! {
! if (VirtualTransactionIdEquals(old_snapshots[j],
! newer_snapshots[k]))
! break;
! }
! if (k >= n_newer_snapshots) /* not there anymore */
! SetInvalidVirtualTransactionId(old_snapshots[j]);
! }
! pfree(newer_snapshots);
! }
!
! if (VirtualTransactionIdIsValid(old_snapshots[i]))
! VirtualXactLock(old_snapshots[i], true);
! }
/*
* Index can now be marked valid -- update its pg_index entry
*/
! index_set_state_flags(indexRelationId, INDEX_CREATE_SET_VALID);
/*
* The pg_index update will cause backends (including this one) to update
--- 753,766 ----
* The index is now valid in the sense that it contains all currently
* interesting tuples. But since it might not contain tuples deleted just
* before the reference snap was taken, we have to wait out any
! * transactions that might have older snapshots.
*/
! WaitForOldSnapshots(limitXmin);
/*
* Index can now be marked valid -- update its pg_index entry
*/
! index_set_state_flags(indexRelationId, INDEX_CREATE_SET_VALID, true);
/*
* The pg_index update will cause backends (including this one) to update
***************
*** 880,885 **** DefineIndex(IndexStmt *stmt,
--- 782,1331 ----
/*
+ * ReindexRelationConcurrently
+ *
+ * Process REINDEX CONCURRENTLY for given relation Oid. The relation can be
+ * either an index or a table. If a table is specified, each reindexing step
+ * is done in parallel with all the table's indexes as well as its dependent
+ * toast indexes.
+ */
+ bool
+ ReindexRelationConcurrently(Oid relationOid)
+ {
+ List *concurrentIndexIds = NIL,
+ *indexIds = NIL,
+ *parentRelationIds = NIL,
+ *lockTags = NIL,
+ *relationLocks = NIL;
+ ListCell *lc, *lc2;
+ Snapshot snapshot;
+ TransactionId limitXmin;
+
+ /*
+ * Extract the list of indexes that are going to be rebuilt based on the
+ * list of relation Oids given by caller. For each element in given list,
+ * If the relkind of given relation Oid is a table, all its valid indexes
+ * will be rebuilt, including its associated toast table indexes. If
+ * relkind is an index, this index itself will be rebuilt. The locks taken
+ * parent relations and involved indexes are kept until this transaction
+ * is committed to protect against schema changes that might occur until
+ * the session lock is taken on each relation.
+ */
+ switch (get_rel_relkind(relationOid))
+ {
+ case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
+ {
+ /*
+ * In the case of a relation, find all its indexes
+ * including toast indexes.
+ */
+ Relation heapRelation = heap_open(relationOid,
+ ShareUpdateExclusiveLock);
+
+ /* Track this relation for session locks */
+ parentRelationIds = lappend_oid(parentRelationIds, relationOid);
+
+ /* Relation on which is based index cannot be shared */
+ if (heapRelation->rd_rel->relisshared)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("concurrent reindex is not supported for shared relations")));
+
+ /* Add all the valid indexes of relation to list */
+ foreach(lc2, RelationGetIndexList(heapRelation))
+ {
+ Oid cellOid = lfirst_oid(lc2);
+ Relation indexRelation = index_open(cellOid,
+ ShareUpdateExclusiveLock);
+
+ if (!indexRelation->rd_index->indisvalid)
+ ereport(WARNING,
+ (errcode(ERRCODE_INDEX_CORRUPTED),
+ errmsg("cannot reindex concurrently invalid index \"%s.%s\", skipping",
+ get_namespace_name(get_rel_namespace(cellOid)),
+ get_rel_name(cellOid))));
+ else
+ indexIds = lappend_oid(indexIds, cellOid);
+
+ index_close(indexRelation, NoLock);
+ }
+
+ /* Also add the toast indexes */
+ if (OidIsValid(heapRelation->rd_rel->reltoastrelid))
+ {
+ Oid toastOid = heapRelation->rd_rel->reltoastrelid;
+ Relation toastRelation = heap_open(toastOid,
+ ShareUpdateExclusiveLock);
+
+ /* Track this relation for session locks */
+ parentRelationIds = lappend_oid(parentRelationIds, toastOid);
+
+ foreach(lc2, RelationGetIndexList(toastRelation))
+ {
+ Oid cellOid = lfirst_oid(lc2);
+ Relation indexRelation = index_open(cellOid,
+ ShareUpdateExclusiveLock);
+
+ if (!indexRelation->rd_index->indisvalid)
+ ereport(WARNING,
+ (errcode(ERRCODE_INDEX_CORRUPTED),
+ errmsg("cannot reindex concurrently invalid index \"%s.%s\", skipping",
+ get_namespace_name(get_rel_namespace(cellOid)),
+ get_rel_name(cellOid))));
+ else
+ indexIds = lappend_oid(indexIds, cellOid);
+
+ index_close(indexRelation, NoLock);
+ }
+
+ heap_close(toastRelation, NoLock);
+ }
+
+ heap_close(heapRelation, NoLock);
+ break;
+ }
+ case RELKIND_INDEX:
+ {
+ /*
+ * For an index simply add its Oid to list. Invalid indexes
+ * cannot be included in list.
+ */
+ Relation indexRelation = index_open(relationOid, ShareUpdateExclusiveLock);
+
+ /* Track the parent relation of this index for session locks */
+ parentRelationIds = list_make1_oid(IndexGetRelation(relationOid, false));
+
+ if (!indexRelation->rd_index->indisvalid)
+ ereport(WARNING,
+ (errcode(ERRCODE_INDEX_CORRUPTED),
+ errmsg("cannot reindex concurrently invalid index \"%s.%s\", skipping",
+ get_namespace_name(get_rel_namespace(relationOid)),
+ get_rel_name(relationOid))));
+ else
+ indexIds = list_make1_oid(relationOid);
+
+ index_close(indexRelation, NoLock);
+ break;
+ }
+ default:
+ /* Return error if type of relation is not supported */
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reindex concurrently this type of relation")));
+ break;
+ }
+
+ /* Definetely no indexes, so leave */
+ if (indexIds == NIL)
+ return false;
+
+ Assert(parentRelationIds != NIL);
+
+ /*
+ * Phase 1 of REINDEX CONCURRENTLY
+ *
+ * Here begins the process for rebuilding concurrently the indexes.
+ * We need first to create an index which is based on the same data
+ * as the former index except that it will be only registered in catalogs
+ * and will be built after. It is possible to perform all the operations
+ * on all the indexes at the same time for a parent relation including
+ * its indexes for toast relation.
+ */
+
+ /* Do the concurrent index creation for each index */
+ foreach(lc, indexIds)
+ {
+ char *concurrentName;
+ Oid indOid = lfirst_oid(lc);
+ Oid concurrentOid = InvalidOid;
+ Relation indexRel,
+ indexParentRel,
+ indexConcurrentRel;
+ LockRelId lockrelid;
+
+ indexRel = index_open(indOid, ShareUpdateExclusiveLock);
+ /* Open the index parent relation, might be a toast or parent relation */
+ indexParentRel = heap_open(indexRel->rd_index->indrelid,
+ ShareUpdateExclusiveLock);
+
+ /* Choose a relation name for concurrent index */
+ concurrentName = ChooseIndexName(get_rel_name(indOid),
+ get_rel_namespace(indexRel->rd_index->indrelid),
+ NULL,
+ false,
+ false,
+ false,
+ true);
+
+ /* Create concurrent index based on given index */
+ concurrentOid = index_concurrent_create(indexParentRel,
+ indOid,
+ concurrentName);
+
+ /*
+ * Now open the relation of concurrent index, a lock is also needed on
+ * it
+ */
+ indexConcurrentRel = index_open(concurrentOid, ShareUpdateExclusiveLock);
+
+ /* Save the concurrent index Oid */
+ concurrentIndexIds = lappend_oid(concurrentIndexIds, concurrentOid);
+
+ /*
+ * Save lockrelid to protect each concurrent relation from drop then
+ * close relations. The lockrelid on parent relation is not taken here
+ * to avoid multiple locks taken on the same relation, instead we rely
+ * on parentRelationIds built earlier.
+ */
+ lockrelid = indexRel->rd_lockInfo.lockRelId;
+ relationLocks = lappend(relationLocks, &lockrelid);
+ lockrelid = indexConcurrentRel->rd_lockInfo.lockRelId;
+ relationLocks = lappend(relationLocks, &lockrelid);
+
+ index_close(indexRel, NoLock);
+ index_close(indexConcurrentRel, NoLock);
+ heap_close(indexParentRel, NoLock);
+ }
+
+ /*
+ * Save the heap lock for following visibility checks with other backends
+ * might conflict with this session.
+ */
+ foreach(lc, parentRelationIds)
+ {
+ Relation heapRelation = heap_open(lfirst_oid(lc), ShareUpdateExclusiveLock);
+ LockRelId lockrelid = heapRelation->rd_lockInfo.lockRelId;
+ LOCKTAG *heaplocktag = (LOCKTAG *) palloc(sizeof(LOCKTAG));
+
+ /* Add lockrelid of parent relation to the list of locked relations */
+ relationLocks = lappend(relationLocks, &lockrelid);
+
+ /* Save the LOCKTAG for this parent relation for the wait phase */
+ SET_LOCKTAG_RELATION(*heaplocktag, lockrelid.dbId, lockrelid.relId);
+ lockTags = lappend(lockTags, heaplocktag);
+
+ /* Close heap relation */
+ heap_close(heapRelation, NoLock);
+ }
+
+ /*
+ * For a concurrent build, it is necessary to make the catalog entries
+ * visible to the other transactions before actually building the index.
+ * This will prevent them from making incompatible HOT updates. The index
+ * is marked as not ready and invalid so as no other transactions will try
+ * to use it for INSERT or SELECT.
+ *
+ * Before committing, get a session level lock on the relation, the
+ * concurrent index and its copy to insure that none of them are dropped
+ * until the operation is done.
+ */
+ foreach(lc, relationLocks)
+ {
+ LockRelId lockRel = * (LockRelId *) lfirst(lc);
+ LockRelationIdForSession(&lockRel, ShareUpdateExclusiveLock);
+ }
+
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+
+ /*
+ * Phase 2 of REINDEX CONCURRENTLY
+ *
+ * Build concurrent indexes in a separate transaction for each index to
+ * avoid having open transactions for an unnecessary long time. A
+ * concurrent build is done for each concurrent index that will replace
+ * the old indexes. Before doing that, we need to wait on the parent
+ * relations until no running transactions could have the parent table
+ * of index open.
+ */
+
+ /* Perform a wait on all the session locks */
+ StartTransactionCommand();
+ WaitForMultipleVirtualLocks(lockTags, ShareLock);
+ CommitTransactionCommand();
+
+ forboth(lc, indexIds, lc2, concurrentIndexIds)
+ {
+ Relation indexRel;
+ Oid indOid = lfirst_oid(lc);
+ Oid concurrentOid = lfirst_oid(lc2);
+ bool primary;
+
+ /* Check for any process interruption */
+ CHECK_FOR_INTERRUPTS();
+
+ /* Start new transaction for this index concurrent build */
+ StartTransactionCommand();
+
+ /* Set ActiveSnapshot since functions in the indexes may need it */
+ PushActiveSnapshot(GetTransactionSnapshot());
+
+ /* Index relation has been closed by previous commit, so reopen it */
+ indexRel = index_open(indOid, ShareUpdateExclusiveLock);
+ primary = indexRel->rd_index->indisprimary;
+ index_close(indexRel, ShareUpdateExclusiveLock);
+
+ /* Perform concurrent build of new index */
+ index_concurrent_build(indexRel->rd_index->indrelid,
+ concurrentOid,
+ primary);
+
+ /*
+ * Update the pg_index row of the concurrent index as ready for inserts.
+ * Once we commit this transaction, any new transactions that open the
+ * table must insert new entries into the index for insertions and
+ * non-HOT updates.
+ */
+ index_set_state_flags(concurrentOid, INDEX_CREATE_SET_READY, true);
+
+ /* we can do away with our snapshot */
+ PopActiveSnapshot();
+
+ /*
+ * Commit this transaction to make the indisready update visible for
+ * concurrent index.
+ */
+ CommitTransactionCommand();
+ }
+
+
+ /*
+ * Phase 3 of REINDEX CONCURRENTLY
+ *
+ * During this phase the concurrent indexes catch up with the INSERT that
+ * might have occurred in the parent table.
+ *
+ * We once again wait until no transaction can have the table open with
+ * the index marked as read-only for updates. Each index validation is done
+ * with a separate transaction to avoid opening transaction for an
+ * unnecessary too long time.
+ */
+
+ /* Perform a wait on all the session locks */
+ StartTransactionCommand();
+ WaitForMultipleVirtualLocks(lockTags, ShareLock);
+ CommitTransactionCommand();
+
+ /*
+ * Perform a scan of each concurrent index with the heap, then insert
+ * any missing index entries.
+ */
+ foreach(lc, concurrentIndexIds)
+ {
+ Oid indOid = lfirst_oid(lc);
+ Oid relOid;
+
+ /* Check for any process interruption */
+ CHECK_FOR_INTERRUPTS();
+
+ /* Open separate transaction to validate index */
+ StartTransactionCommand();
+
+ /* Get the parent relation Oid */
+ relOid = IndexGetRelation(indOid, false);
+
+ /*
+ * Take the reference snapshot that will be used for the concurrent indexes
+ * validation.
+ */
+ snapshot = RegisterSnapshot(GetTransactionSnapshot());
+ PushActiveSnapshot(snapshot);
+
+ /* Validate index, which might be a toast */
+ validate_index(relOid, indOid, snapshot);
+
+ /*
+ * We can now do away with our active snapshot, we still need to save the xmin
+ * limit to wait for older snapshots.
+ */
+ limitXmin = snapshot->xmin;
+ PopActiveSnapshot();
+
+ /* And we can remove the validating snapshot too */
+ UnregisterSnapshot(snapshot);
+
+ /*
+ * This concurrent index is now valid as they contain all the tuples
+ * necessary. However, it might not have taken into account deleted tuples
+ * before the reference snapshot was taken, so we need to wait for the
+ * transactions that might have older snapshots than ours.
+ */
+ WaitForOldSnapshots(limitXmin);
+
+ /* Commit this transaction to make the concurrent index valid */
+ CommitTransactionCommand();
+ }
+
+ /*
+ * Phase 4 of REINDEX CONCURRENTLY
+ *
+ * Now that the concurrent indexes are valid and can be used, we need to
+ * swap each concurrent index with its corresponding old index. The
+ * concurrent index is marked as valid before performing the swap, and
+ * is invalidated once the swap is done, making it not usable by other
+ * backends once its associated transaction is committed.
+ */
+
+ /* Swap the indexes and mark the indexes that have the old data as invalid */
+ forboth(lc, indexIds, lc2, concurrentIndexIds)
+ {
+ Oid indOid = lfirst_oid(lc);
+ Oid concurrentOid = lfirst_oid(lc2);
+ Relation indexRel, indexParentRel;
+
+ /* Check for any process interruption */
+ CHECK_FOR_INTERRUPTS();
+
+ /*
+ * Each index needs to be swapped in a separate transaction, so start
+ * a new one.
+ */
+ StartTransactionCommand();
+
+ /*
+ * Mark the cache of associated relation as invalid, open relation
+ * relations. AccessExclusive Lock is taken here and not a lower lock
+ * to reduce likelihood of deadlock as ShareUpdateExclusiveLock is
+ * already taken within session.
+ */
+ indexRel = index_open(indOid, AccessExclusiveLock);
+ indexParentRel = heap_open(indexRel->rd_index->indrelid,
+ AccessExclusiveLock);
+
+ /*
+ * Concurrent index can now be marked as valid before performing
+ * the swap. Note here that as an exclusive lock is taken on the
+ * relations involved it is safer to call this function as it would
+ * be for a non-concurrent context.
+ * Note: With MVCC catalog access, a lower lock would be enough.
+ */
+ index_set_state_flags(concurrentOid, INDEX_CREATE_SET_VALID, false);
+
+ /* Swap old index and its concurrent */
+ index_concurrent_swap(concurrentOid, indOid);
+
+ /*
+ * Now mark the old index as invalid, the swap is done.
+ */
+ index_concurrent_clear_valid(indexParentRel, concurrentOid, false);
+
+ /*
+ * Invalidate the relcache for the table, so that after this commit
+ * all sessions will refresh any cached plans that might reference the
+ * index.
+ */
+ CacheInvalidateRelcache(indexParentRel);
+
+ /* Close relations opened previously for cache invalidation */
+ index_close(indexRel, NoLock);
+ heap_close(indexParentRel, NoLock);
+
+ /* Commit this transaction and make old index invalidation visible */
+ CommitTransactionCommand();
+ }
+
+ /*
+ * Phase 5 of REINDEX CONCURRENTLY
+ *
+ * The concurrent indexes now hold the old relfilenode of the other indexes
+ * transactions that might use them. Each operation is performed with a
+ * separate transaction.
+ */
+
+ /* Now mark the concurrent indexes as not ready */
+ foreach(lc, concurrentIndexIds)
+ {
+ Oid indOid = lfirst_oid(lc);
+ Oid relOid;
+ LOCKTAG *heapLockTag = NULL;
+ ListCell *cell;
+
+ /* Check for any process interruption */
+ CHECK_FOR_INTERRUPTS();
+
+ StartTransactionCommand();
+ relOid = IndexGetRelation(indOid, false);
+
+ /*
+ * Find the locktag of parent table for this index, we need to wait for
+ * locks on it.
+ */
+ foreach(cell, lockTags)
+ {
+ LOCKTAG *localTag = (LOCKTAG *) lfirst(cell);
+ if (relOid == localTag->locktag_field2)
+ heapLockTag = localTag;
+ }
+ Assert(heapLockTag && heapLockTag->locktag_field2 != InvalidOid);
+
+ /*
+ * Finish the index invalidation and set it as dead. Note that it is
+ * necessary to wait for for virtual locks on the parent relation
+ * before setting the index as dead.
+ */
+ index_concurrent_set_dead(indOid, relOid, *heapLockTag);
+
+ /* Commit this transaction to make the update visible. */
+ CommitTransactionCommand();
+ }
+
+ /*
+ * Phase 6 of REINDEX CONCURRENTLY
+ *
+ * Drop the concurrent indexes. This needs to be done through
+ * performDeletion or related dependencies will not be dropped for the old
+ * indexes. The internal mechanism of DROP INDEX CONCURRENTLY is not used
+ * as here the indexes are already considered as dead and invalid, so they
+ * will not be used by other backends.
+ */
+ foreach(lc, concurrentIndexIds)
+ {
+ Oid indexOid = lfirst_oid(lc);
+
+ /* Check for any process interruption */
+ CHECK_FOR_INTERRUPTS();
+
+ /* Start transaction to drop this index */
+ StartTransactionCommand();
+
+ /* Get fresh snapshot for next step */
+ PushActiveSnapshot(GetTransactionSnapshot());
+
+ /*
+ * Open transaction if necessary, for the first index treated its
+ * transaction has been already opened previously.
+ */
+ index_concurrent_drop(indexOid);
+
+ /* We can do away with our snapshot */
+ PopActiveSnapshot();
+
+ /* Commit this transaction to make the update visible. */
+ CommitTransactionCommand();
+ }
+
+ /*
+ * Last thing to do is release the session-level lock on the parent table
+ * and the indexes of table.
+ */
+ foreach(lc, relationLocks)
+ {
+ LockRelId lockRel = * (LockRelId *) lfirst(lc);
+ UnlockRelationIdForSession(&lockRel, ShareUpdateExclusiveLock);
+ }
+
+ /* Start a new transaction to finish process properly */
+ StartTransactionCommand();
+
+ /* Get fresh snapshot for the end of process */
+ PushActiveSnapshot(GetTransactionSnapshot());
+
+ return true;
+ }
+
+
+ /*
* CheckMutability
* Test whether given expression is mutable
*/
***************
*** 1542,1548 **** ChooseRelationName(const char *name1, const char *name2,
static char *
ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
! bool primary, bool isconstraint)
{
char *indexname;
--- 1988,1995 ----
static char *
ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
! bool primary, bool isconstraint,
! bool concurrent)
{
char *indexname;
***************
*** 1568,1573 **** ChooseIndexName(const char *tabname, Oid namespaceId,
--- 2015,2027 ----
"key",
namespaceId);
}
+ else if (concurrent)
+ {
+ indexname = ChooseRelationName(tabname,
+ NULL,
+ "cct",
+ namespaceId);
+ }
else
{
indexname = ChooseRelationName(tabname,
***************
*** 1680,1697 **** ChooseIndexColumnNames(List *indexElems)
* Recreate a specific index.
*/
Oid
! ReindexIndex(RangeVar *indexRelation)
{
Oid indOid;
Oid heapOid = InvalidOid;
! /* lock level used here should match index lock reindex_index() */
! indOid = RangeVarGetRelidExtended(indexRelation, AccessExclusiveLock,
! false, false,
! RangeVarCallbackForReindexIndex,
! (void *) &heapOid);
! reindex_index(indOid, false);
return indOid;
}
--- 2134,2155 ----
* Recreate a specific index.
*/
Oid
! ReindexIndex(RangeVar *indexRelation, bool concurrent)
{
Oid indOid;
Oid heapOid = InvalidOid;
! indOid = RangeVarGetRelidExtended(indexRelation,
! concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock,
! false, false,
! RangeVarCallbackForReindexIndex,
! (void *) &heapOid);
! /* Continue process for concurrent or non-concurrent case */
! if (!concurrent)
! reindex_index(indOid, false);
! else
! ReindexRelationConcurrently(indOid);
return indOid;
}
***************
*** 1760,1772 **** RangeVarCallbackForReindexIndex(const RangeVar *relation,
* Recreate all indexes of a table (and of its toast table, if any)
*/
Oid
! ReindexTable(RangeVar *relation)
{
Oid heapOid;
/* The lock level used here should match reindex_relation(). */
! heapOid = RangeVarGetRelidExtended(relation, ShareLock, false, false,
! RangeVarCallbackOwnsTable, NULL);
if (!reindex_relation(heapOid, REINDEX_REL_PROCESS_TOAST))
ereport(NOTICE,
--- 2218,2244 ----
* Recreate all indexes of a table (and of its toast table, if any)
*/
Oid
! ReindexTable(RangeVar *relation, bool concurrent)
{
Oid heapOid;
/* The lock level used here should match reindex_relation(). */
! heapOid = RangeVarGetRelidExtended(relation,
! concurrent ? ShareUpdateExclusiveLock : ShareLock,
! false, false,
! RangeVarCallbackOwnsTable, NULL);
!
! /* Run through the concurrent process if necessary */
! if (concurrent)
! {
! if (!ReindexRelationConcurrently(heapOid))
! {
! ereport(NOTICE,
! (errmsg("table \"%s\" has no indexes",
! relation->relname)));
! }
! return heapOid;
! }
if (!reindex_relation(heapOid, REINDEX_REL_PROCESS_TOAST))
ereport(NOTICE,
***************
*** 1785,1791 **** ReindexTable(RangeVar *relation)
* That means this must not be called within a user transaction block!
*/
Oid
! ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
{
Relation relationRelation;
HeapScanDesc scan;
--- 2257,2266 ----
* That means this must not be called within a user transaction block!
*/
Oid
! ReindexDatabase(const char *databaseName,
! bool do_system,
! bool do_user,
! bool concurrent)
{
Relation relationRelation;
HeapScanDesc scan;
***************
*** 1797,1802 **** ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
--- 2272,2286 ----
AssertArg(databaseName);
+ /*
+ * CONCURRENTLY operation is not allowed for a system, but it is for a
+ * database.
+ */
+ if (concurrent && !do_user)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot reindex system concurrently")));
+
if (strcmp(databaseName, get_database_name(MyDatabaseId)) != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
***************
*** 1880,1894 **** ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
foreach(l, relids)
{
Oid relid = lfirst_oid(l);
StartTransactionCommand();
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
! if (reindex_relation(relid, REINDEX_REL_PROCESS_TOAST))
ereport(NOTICE,
! (errmsg("table \"%s.%s\" was reindexed",
get_namespace_name(get_rel_namespace(relid)),
! get_rel_name(relid))));
PopActiveSnapshot();
CommitTransactionCommand();
}
--- 2364,2403 ----
foreach(l, relids)
{
Oid relid = lfirst_oid(l);
+ bool result = false;
+ bool process_concurrent;
StartTransactionCommand();
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
!
! /* Determine if relation needs to be processed concurrently */
! process_concurrent = concurrent &&
! !IsSystemNamespace(get_rel_namespace(relid));
!
! /*
! * Reindex relation with a concurrent or non-concurrent process.
! * System relations cannot be reindexed concurrently, but they
! * need to be reindexed including pg_class with a normal process
! * as they could be corrupted, and concurrent process might also
! * use them. This does not include toast relations, which are
! * reindexed when their parent relation is processed.
! */
! if (process_concurrent)
! {
! old = MemoryContextSwitchTo(private_context);
! result = ReindexRelationConcurrently(relid);
! MemoryContextSwitchTo(old);
! }
! else
! result = reindex_relation(relid, REINDEX_REL_PROCESS_TOAST);
!
! if (result)
ereport(NOTICE,
! (errmsg("table \"%s.%s\" was reindexed%s",
get_namespace_name(get_rel_namespace(relid)),
! get_rel_name(relid),
! process_concurrent ? " concurrently" : "")));
PopActiveSnapshot();
CommitTransactionCommand();
}
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 900,905 **** RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
--- 900,937 ----
if (classform->relkind != relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
+ /*
+ * Check the case of a system index that might have been invalidated by a
+ * failed concurrent process and allow its drop. For the time being, this
+ * only concerns indexes of toast relations that became invalid during a
+ * REINDEX CONCURRENTLY process.
+ */
+ if (IsSystemClass(classform) &&
+ relkind == RELKIND_INDEX)
+ {
+ HeapTuple locTuple;
+ Form_pg_index indexform;
+ bool indisvalid;
+
+ locTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(state->heapOid));
+ if (!HeapTupleIsValid(locTuple))
+ {
+ ReleaseSysCache(tuple);
+ return;
+ }
+
+ indexform = (Form_pg_index) GETSTRUCT(locTuple);
+ indisvalid = indexform->indisvalid;
+ ReleaseSysCache(locTuple);
+
+ /* Leave if index entry is not valid */
+ if (!indisvalid)
+ {
+ ReleaseSysCache(tuple);
+ return;
+ }
+ }
+
/* Allow DROP to either table owner or schema owner */
if (!pg_class_ownercheck(relOid, GetUserId()) &&
!pg_namespace_ownercheck(classform->relnamespace, GetUserId()))
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 1201,1206 **** check_exclusion_constraint(Relation heap, Relation index, IndexInfo *indexInfo,
--- 1201,1220 ----
}
/*
+ * As an invalid index only exists when created in a concurrent context,
+ * and that this code path cannot be taken by CREATE INDEX CONCURRENTLY
+ * as this feature is not available for exclusion constraints, this code
+ * path can only be taken by REINDEX CONCURRENTLY. In this case the same
+ * index exists in parallel to this one so we can bypass this check as
+ * it has already been done on the other index existing in parallel.
+ * If exclusion constraints are supported in the future for CREATE INDEX
+ * CONCURRENTLY, this should be removed or completed especially for this
+ * purpose.
+ */
+ if (!index->rd_index->indisvalid)
+ return true;
+
+ /*
* Search the tuples that are in the index for any violations, including
* tuples that aren't visible yet.
*/
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 3617,3622 **** _copyReindexStmt(const ReindexStmt *from)
--- 3617,3623 ----
COPY_STRING_FIELD(name);
COPY_SCALAR_FIELD(do_system);
COPY_SCALAR_FIELD(do_user);
+ COPY_SCALAR_FIELD(concurrent);
return newnode;
}
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1839,1844 **** _equalReindexStmt(const ReindexStmt *a, const ReindexStmt *b)
--- 1839,1845 ----
COMPARE_STRING_FIELD(name);
COMPARE_SCALAR_FIELD(do_system);
COMPARE_SCALAR_FIELD(do_user);
+ COMPARE_SCALAR_FIELD(concurrent);
return true;
}
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 6752,6780 **** opt_if_exists: IF_P EXISTS { $$ = TRUE; }
*****************************************************************************/
ReindexStmt:
! REINDEX reindex_type qualified_name opt_force
{
ReindexStmt *n = makeNode(ReindexStmt);
n->kind = $2;
! n->relation = $3;
n->name = NULL;
$$ = (Node *)n;
}
! | REINDEX SYSTEM_P name opt_force
{
ReindexStmt *n = makeNode(ReindexStmt);
n->kind = OBJECT_DATABASE;
! n->name = $3;
n->relation = NULL;
n->do_system = true;
n->do_user = false;
$$ = (Node *)n;
}
! | REINDEX DATABASE name opt_force
{
ReindexStmt *n = makeNode(ReindexStmt);
n->kind = OBJECT_DATABASE;
! n->name = $3;
n->relation = NULL;
n->do_system = true;
n->do_user = true;
--- 6752,6783 ----
*****************************************************************************/
ReindexStmt:
! REINDEX reindex_type opt_concurrently qualified_name opt_force
{
ReindexStmt *n = makeNode(ReindexStmt);
n->kind = $2;
! n->concurrent = $3;
! n->relation = $4;
n->name = NULL;
$$ = (Node *)n;
}
! | REINDEX SYSTEM_P opt_concurrently name opt_force
{
ReindexStmt *n = makeNode(ReindexStmt);
n->kind = OBJECT_DATABASE;
! n->concurrent = $3;
! n->name = $4;
n->relation = NULL;
n->do_system = true;
n->do_user = false;
$$ = (Node *)n;
}
! | REINDEX DATABASE opt_concurrently name opt_force
{
ReindexStmt *n = makeNode(ReindexStmt);
n->kind = OBJECT_DATABASE;
! n->concurrent = $3;
! n->name = $4;
n->relation = NULL;
n->do_system = true;
n->do_user = true;
*** a/src/backend/storage/ipc/procarray.c
--- b/src/backend/storage/ipc/procarray.c
***************
*** 2526,2531 **** XidCacheRemoveRunningXids(TransactionId xid,
--- 2526,2677 ----
LWLockRelease(ProcArrayLock);
}
+
+ /*
+ * WaitForMultipleVirtualLocks
+ *
+ * Wait until no transactions hold the relation related to lock those locks.
+ * To do this, inquire which xacts currently would conflict with each lock on
+ * the table referred by the respective LOCKTAG -- ie, which ones have a lock
+ * that permits writing the relation. Then wait for each of these xacts to
+ * commit or abort.
+ *
+ * To do this, inquire which xacts currently would conflict with lockmode
+ * on the relation.
+ *
+ * Note: GetLockConflicts() never reports our own xid, hence we need not
+ * check for that. Also, prepared xacts are not reported, which is fine
+ * since they certainly aren't going to do anything more.
+ */
+ void
+ WaitForMultipleVirtualLocks(List *locktags, LOCKMODE lockmode)
+ {
+ VirtualTransactionId **old_lockholders;
+ int i, count = 0;
+ ListCell *lc;
+
+ /* Leave if no locks to wait for */
+ if (list_length(locktags) == 0)
+ return;
+
+ old_lockholders = (VirtualTransactionId **)
+ palloc(list_length(locktags) * sizeof(VirtualTransactionId *));
+
+ /* Collect the transactions we need to wait on for each relation lock */
+ foreach(lc, locktags)
+ {
+ LOCKTAG *locktag = lfirst(lc);
+ old_lockholders[count++] = GetLockConflicts(locktag, lockmode);
+ }
+
+ /* Finally wait for each transaction to complete */
+ for (i = 0; i < count; i++)
+ {
+ VirtualTransactionId *lockholders = old_lockholders[i];
+
+ while (VirtualTransactionIdIsValid(*lockholders))
+ {
+ VirtualXactLock(*lockholders, true);
+ lockholders++;
+ }
+ }
+
+ pfree(old_lockholders);
+ }
+
+
+ /*
+ * WaitForVirtualLocks
+ *
+ * Similar to WaitForMultipleVirtualLocks, but for a single lock.
+ */
+ void
+ WaitForVirtualLocks(LOCKTAG heaplocktag, LOCKMODE lockmode)
+ {
+ WaitForMultipleVirtualLocks(list_make1(&heaplocktag), lockmode);
+ }
+
+
+ /*
+ * WaitForOldSnapshots
+ *
+ * Wait for transactions that might have older snapshot than the given xmin
+ * limit, because it might not contain tuples deleted just before it has
+ * been taken. Obtain a list of VXIDs of such transactions, and wait for them
+ * individually.
+ *
+ * We can exclude any running transactions that have xmin > the xmin given;
+ * their oldest snapshot must be newer than our xmin limit.
+ * We can also exclude any transactions that have xmin = zero, since they
+ * evidently have no live snapshot at all (and any one they might be in
+ * process of taking is certainly newer than ours). Transactions in other
+ * DBs can be ignored too, since they'll never even be able to see this
+ * index.
+ *
+ * We can also exclude autovacuum processes and processes running manual
+ * lazy VACUUMs, because they won't be fazed by missing index entries
+ * either. (Manual ANALYZEs, however, can't be excluded because they
+ * might be within transactions that are going to do arbitrary operations
+ * later.)
+ *
+ * Also, GetCurrentVirtualXIDs never reports our own vxid, so we need not
+ * check for that.
+ *
+ * If a process goes idle-in-transaction with xmin zero, we do not need to
+ * wait for it anymore, per the above argument. We do not have the
+ * infrastructure right now to stop waiting if that happens, but we can at
+ * least avoid the folly of waiting when it is idle at the time we would
+ * begin to wait. We do this by repeatedly rechecking the output of
+ * GetCurrentVirtualXIDs. If, during any iteration, a particular vxid
+ * doesn't show up in the output, we know we can forget about it.
+ */
+ void
+ WaitForOldSnapshots(TransactionId limitXmin)
+ {
+ int i, n_old_snapshots;
+ VirtualTransactionId *old_snapshots;
+
+ old_snapshots = GetCurrentVirtualXIDs(limitXmin, true, false,
+ PROC_IS_AUTOVACUUM | PROC_IN_VACUUM,
+ &n_old_snapshots);
+
+ for (i = 0; i < n_old_snapshots; i++)
+ {
+ if (!VirtualTransactionIdIsValid(old_snapshots[i]))
+ continue; /* found uninteresting in previous cycle */
+
+ if (i > 0)
+ {
+ /* see if anything's changed ... */
+ VirtualTransactionId *newer_snapshots;
+ int n_newer_snapshots, j, k;
+
+ newer_snapshots = GetCurrentVirtualXIDs(limitXmin,
+ true, false,
+ PROC_IS_AUTOVACUUM | PROC_IN_VACUUM,
+ &n_newer_snapshots);
+ for (j = i; j < n_old_snapshots; j++)
+ {
+ if (!VirtualTransactionIdIsValid(old_snapshots[j]))
+ continue; /* found uninteresting in previous cycle */
+ for (k = 0; k < n_newer_snapshots; k++)
+ {
+ if (VirtualTransactionIdEquals(old_snapshots[j],
+ newer_snapshots[k]))
+ break;
+ }
+ if (k >= n_newer_snapshots) /* not there anymore */
+ SetInvalidVirtualTransactionId(old_snapshots[j]);
+ }
+ pfree(newer_snapshots);
+ }
+
+ if (VirtualTransactionIdIsValid(old_snapshots[i]))
+ VirtualXactLock(old_snapshots[i], true);
+ }
+ }
+
+
#ifdef XIDCACHE_DEBUG
/*
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 778,793 **** standard_ProcessUtility(Node *parsetree,
{
ReindexStmt *stmt = (ReindexStmt *) parsetree;
/* we choose to allow this during "read only" transactions */
PreventCommandDuringRecovery("REINDEX");
switch (stmt->kind)
{
case OBJECT_INDEX:
! ReindexIndex(stmt->relation);
break;
case OBJECT_TABLE:
case OBJECT_MATVIEW:
! ReindexTable(stmt->relation);
break;
case OBJECT_DATABASE:
--- 778,797 ----
{
ReindexStmt *stmt = (ReindexStmt *) parsetree;
+ if (stmt->concurrent)
+ PreventTransactionChain(isTopLevel,
+ "REINDEX CONCURRENTLY");
+
/* we choose to allow this during "read only" transactions */
PreventCommandDuringRecovery("REINDEX");
switch (stmt->kind)
{
case OBJECT_INDEX:
! ReindexIndex(stmt->relation, stmt->concurrent);
break;
case OBJECT_TABLE:
case OBJECT_MATVIEW:
! ReindexTable(stmt->relation, stmt->concurrent);
break;
case OBJECT_DATABASE:
***************
*** 799,806 **** standard_ProcessUtility(Node *parsetree,
*/
PreventTransactionChain(isTopLevel,
"REINDEX DATABASE");
! ReindexDatabase(stmt->name,
! stmt->do_system, stmt->do_user);
break;
default:
elog(ERROR, "unrecognized object type: %d",
--- 803,810 ----
*/
PreventTransactionChain(isTopLevel,
"REINDEX DATABASE");
! ReindexDatabase(stmt->name, stmt->do_system,
! stmt->do_user, stmt->concurrent);
break;
default:
elog(ERROR, "unrecognized object type: %d",
*** a/src/include/catalog/index.h
--- b/src/include/catalog/index.h
***************
*** 60,66 **** extern Oid index_create(Relation heapRelation,
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
! bool is_internal);
extern void index_constraint_create(Relation heapRelation,
Oid indexRelationId,
--- 60,87 ----
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
! bool is_internal,
! bool is_reindex);
!
! extern Oid index_concurrent_create(Relation heapRelation,
! Oid indOid,
! char *concurrentName);
!
! extern void index_concurrent_build(Oid heapOid,
! Oid indexOid,
! bool isprimary);
!
! extern void index_concurrent_swap(Oid newIndexOid, Oid oldIndexOid);
!
! extern void index_concurrent_set_dead(Oid indexId,
! Oid heapId,
! LOCKTAG locktag);
!
! extern void index_concurrent_clear_valid(Relation heapRelation,
! Oid indexOid,
! bool concurrent);
!
! extern void index_concurrent_drop(Oid indexOid);
extern void index_constraint_create(Relation heapRelation,
Oid indexRelationId,
***************
*** 100,106 **** extern double IndexBuildHeapScan(Relation heapRelation,
extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
! extern void index_set_state_flags(Oid indexId, IndexStateFlagsAction action);
extern void reindex_index(Oid indexId, bool skip_constraint_checks);
--- 121,129 ----
extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
! extern void index_set_state_flags(Oid indexId,
! IndexStateFlagsAction action,
! bool concurrent);
extern void reindex_index(Oid indexId, bool skip_constraint_checks);
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 26,35 **** extern Oid DefineIndex(IndexStmt *stmt,
bool check_rights,
bool skip_build,
bool quiet);
! extern Oid ReindexIndex(RangeVar *indexRelation);
! extern Oid ReindexTable(RangeVar *relation);
extern Oid ReindexDatabase(const char *databaseName,
! bool do_system, bool do_user);
extern char *makeObjectName(const char *name1, const char *name2,
const char *label);
extern char *ChooseRelationName(const char *name1, const char *name2,
--- 26,36 ----
bool check_rights,
bool skip_build,
bool quiet);
! extern Oid ReindexIndex(RangeVar *indexRelation, bool concurrent);
! extern Oid ReindexTable(RangeVar *relation, bool concurrent);
extern Oid ReindexDatabase(const char *databaseName,
! bool do_system, bool do_user, bool concurrent);
! extern bool ReindexRelationConcurrently(Oid relOid);
extern char *makeObjectName(const char *name1, const char *name2,
const char *label);
extern char *ChooseRelationName(const char *name1, const char *name2,
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2538,2543 **** typedef struct ReindexStmt
--- 2538,2544 ----
const char *name; /* name of database to reindex */
bool do_system; /* include system tables in database case */
bool do_user; /* include user tables in database case */
+ bool concurrent; /* reindex concurrently? */
} ReindexStmt;
/* ----------------------
*** a/src/include/storage/procarray.h
--- b/src/include/storage/procarray.h
***************
*** 76,79 **** extern void XidCacheRemoveRunningXids(TransactionId xid,
--- 76,83 ----
int nxids, const TransactionId *xids,
TransactionId latestXid);
+ extern void WaitForMultipleVirtualLocks(List *locktags, LOCKMODE lockmode);
+ extern void WaitForVirtualLocks(LOCKTAG heaplocktag, LOCKMODE lockmode);
+ extern void WaitForOldSnapshots(TransactionId limitXmin);
+
#endif /* PROCARRAY_H */
*** a/src/test/regress/expected/create_index.out
--- b/src/test/regress/expected/create_index.out
***************
*** 2721,2723 **** ORDER BY thousand;
--- 2721,2778 ----
1 | 1001
(2 rows)
+ --
+ -- Check behavior of REINDEX and REINDEX CONCURRENTLY
+ --
+ CREATE TABLE concur_reindex_tab (c1 int);
+ -- REINDEX
+ REINDEX TABLE concur_reindex_tab; -- notice
+ NOTICE: table "concur_reindex_tab" has no indexes
+ REINDEX TABLE CONCURRENTLY concur_reindex_tab; -- notice
+ NOTICE: table "concur_reindex_tab" has no indexes
+ ALTER TABLE concur_reindex_tab ADD COLUMN c2 text; -- add toast index
+ -- Normal index with integer column
+ CREATE UNIQUE INDEX concur_reindex_ind1 ON concur_reindex_tab(c1);
+ -- Normal index with text column
+ CREATE INDEX concur_reindex_ind2 ON concur_reindex_tab(c2);
+ -- UNIQUE index with expression
+ CREATE UNIQUE INDEX concur_reindex_ind3 ON concur_reindex_tab(abs(c1));
+ -- Duplicate column names
+ CREATE INDEX concur_reindex_ind4 ON concur_reindex_tab(c1, c1, c2);
+ -- Create table for check on foreign key dependence switch with indexes swapped
+ ALTER TABLE concur_reindex_tab ADD PRIMARY KEY USING INDEX concur_reindex_ind1;
+ CREATE TABLE concur_reindex_tab2 (c1 int REFERENCES concur_reindex_tab);
+ INSERT INTO concur_reindex_tab VALUES (1, 'a');
+ INSERT INTO concur_reindex_tab VALUES (2, 'a');
+ -- Check materialized views
+ CREATE MATERIALIZED VIEW concur_reindex_matview AS SELECT * FROM concur_reindex_tab;
+ REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
+ REINDEX TABLE CONCURRENTLY concur_reindex_tab;
+ REINDEX TABLE CONCURRENTLY concur_reindex_matview;
+ -- Check errors
+ -- Cannot run inside a transaction block
+ BEGIN;
+ REINDEX TABLE CONCURRENTLY concur_reindex_tab;
+ ERROR: REINDEX CONCURRENTLY cannot run inside a transaction block
+ COMMIT;
+ REINDEX TABLE CONCURRENTLY pg_database; -- no shared relation
+ ERROR: concurrent reindex is not supported for shared relations
+ REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
+ ERROR: cannot reindex system concurrently
+ -- Check the relation status, there should not be invalid indexes
+ \d concur_reindex_tab
+ Table "public.concur_reindex_tab"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ c1 | integer | not null
+ c2 | text |
+ Indexes:
+ "concur_reindex_ind1" PRIMARY KEY, btree (c1)
+ "concur_reindex_ind3" UNIQUE, btree (abs(c1))
+ "concur_reindex_ind2" btree (c2)
+ "concur_reindex_ind4" btree (c1, c1, c2)
+ Referenced by:
+ TABLE "concur_reindex_tab2" CONSTRAINT "concur_reindex_tab2_c1_fkey" FOREIGN KEY (c1) REFERENCES concur_reindex_tab(c1)
+
+ DROP MATERIALIZED VIEW concur_reindex_matview;
+ DROP TABLE concur_reindex_tab, concur_reindex_tab2;
*** a/src/test/regress/sql/create_index.sql
--- b/src/test/regress/sql/create_index.sql
***************
*** 912,914 **** ORDER BY thousand;
--- 912,954 ----
SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
+
+ --
+ -- Check behavior of REINDEX and REINDEX CONCURRENTLY
+ --
+ CREATE TABLE concur_reindex_tab (c1 int);
+ -- REINDEX
+ REINDEX TABLE concur_reindex_tab; -- notice
+ REINDEX TABLE CONCURRENTLY concur_reindex_tab; -- notice
+ ALTER TABLE concur_reindex_tab ADD COLUMN c2 text; -- add toast index
+ -- Normal index with integer column
+ CREATE UNIQUE INDEX concur_reindex_ind1 ON concur_reindex_tab(c1);
+ -- Normal index with text column
+ CREATE INDEX concur_reindex_ind2 ON concur_reindex_tab(c2);
+ -- UNIQUE index with expression
+ CREATE UNIQUE INDEX concur_reindex_ind3 ON concur_reindex_tab(abs(c1));
+ -- Duplicate column names
+ CREATE INDEX concur_reindex_ind4 ON concur_reindex_tab(c1, c1, c2);
+ -- Create table for check on foreign key dependence switch with indexes swapped
+ ALTER TABLE concur_reindex_tab ADD PRIMARY KEY USING INDEX concur_reindex_ind1;
+ CREATE TABLE concur_reindex_tab2 (c1 int REFERENCES concur_reindex_tab);
+ INSERT INTO concur_reindex_tab VALUES (1, 'a');
+ INSERT INTO concur_reindex_tab VALUES (2, 'a');
+ -- Check materialized views
+ CREATE MATERIALIZED VIEW concur_reindex_matview AS SELECT * FROM concur_reindex_tab;
+ REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
+ REINDEX TABLE CONCURRENTLY concur_reindex_tab;
+ REINDEX TABLE CONCURRENTLY concur_reindex_matview;
+
+ -- Check errors
+ -- Cannot run inside a transaction block
+ BEGIN;
+ REINDEX TABLE CONCURRENTLY concur_reindex_tab;
+ COMMIT;
+ REINDEX TABLE CONCURRENTLY pg_database; -- no shared relation
+ REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
+
+ -- Check the relation status, there should not be invalid indexes
+ \d concur_reindex_tab
+ DROP MATERIALIZED VIEW concur_reindex_matview;
+ DROP TABLE concur_reindex_tab, concur_reindex_tab2;