diff --git a/doc/src/sgml/ref/reindex.sgml b/doc/src/sgml/ref/reindex.sgml
index 7222665..2931329 100644
--- a/doc/src/sgml/ref/reindex.sgml
+++ b/doc/src/sgml/ref/reindex.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
-REINDEX { INDEX | TABLE | DATABASE | SYSTEM } name [ FORCE ]
+REINDEX { INDEX | TABLE | DATABASE | SYSTEM } name [ FORCE ] [ CONCURRENTLY ]
@@ -68,9 +68,10 @@ REINDEX { INDEX | TABLE | DATABASE | SYSTEM } nam
An index build with the CONCURRENTLY option failed, leaving
an invalid index. Such indexes are useless but it can be
convenient to use REINDEX to rebuild them. Note that
- REINDEX will not perform a concurrent build. To build the
- index without interfering with production you should drop the index and
- reissue the CREATE INDEX CONCURRENTLY command.
+ REINDEX will not perform a concurrent build if
+ CONCURRENTLY is not specified. To build the index without interfering
+ with production you should drop the index and reissue the CREATE
+ INDEX CONCURRENTLY or REINDEX CONCURRENTLY command.
@@ -139,6 +140,21 @@ REINDEX { INDEX | TABLE | DATABASE | SYSTEM } nam
+ CONCURRENTLY
+
+
+ When this option is used, 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 .
+
+
+
+
+
FORCE
@@ -231,6 +247,93 @@ REINDEX { INDEX | TABLE | DATABASE | SYSTEM } nam
to be reindexed by separate commands. This is still possible, but
redundant.
+
+
+
+ Rebuilding Indexes Concurrently
+
+
+ index
+ rebuilding concurrently
+
+
+
+ Rebuilding an index can interfere with regular operation of a database.
+ Normally 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.
+
+
+
+ PostgreSQL supports rebuilding indexes without locking
+ out writes. This method is invoked by specifying the
+ CONCURRENTLY option of REINDEX.
+ When this option is used, 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 transactiions 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.
+
+
+
+ In a concurrent index build, a new index that 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 and to make the new
+ index valid from the other backends. Once this is performed, the old
+ and fresh indexes are swapped in, and the old index is marked as invalid
+ in a third transaction. Finally two additional transactions are used to mark
+ the old index as not ready and then drop it.
+
+
+
+ If a problem arises while rebuilding the indexes, such as a
+ uniqueness violation in a unique index, the REINDEX
+ command will fail but leave behind an 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 psql \d command will report
+ such an index as INVALID:
+
+
+postgres=# \d tab
+ Table "public.tab"
+ Column | Type | Modifiers
+--------+---------+-----------
+ col | integer |
+Indexes:
+ "idx" btree (col)
+ "idx_cct" btree (col) INVALID
+
+
+ The recommended recovery method in such cases is to drop the concurrent
+ index and try again to perform REINDEX CONCURRENTLY once again.
+ The concurrent index created during the processing has a name finishing by
+ the suffix cct.
+
+
+
+ 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 REINDEX TABLE or REINDEX INDEX
+ command can be performed within a transaction block, but
+ REINDEX CONCURRENTLY cannot. REINDEX DATABASE is
+ by default not allowed to run inside a transaction block, so in this case
+ CONCURRENTLY is not supported.
+
+
+
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 464950b..ca41255 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1076,6 +1076,267 @@ index_create(Relation heapRelation,
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;
+ int i;
+ HeapTuple indexTuple;
+ Datum indclassDatum, indoptionDatum;
+ oidvector *indclass;
+ int2vector *indcoloptions;
+ bool isnull;
+
+ indexRelation = index_open(indOid, RowExclusiveLock);
+
+ /* Concurrent index uses the same index information as former index */
+ indexInfo = BuildIndexInfo(indexRelation);
+
+ /* Build the list of column names, necessary for index_create */
+ for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+ {
+ AttrNumber attnum = indexInfo->ii_KeyAttrNumbers[i];
+ Form_pg_attribute attform = heapRelation->rd_att->attrs[attnum - 1];;
+
+ /* Pick up column name from the relation */
+ columnNames = lappend(columnNames, pstrdup(NameStr(attform->attname)));
+ }
+
+ /* 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);
+
+ indoptionDatum = SysCacheGetAttr(INDEXRELID, indexTuple,
+ Anum_pg_index_indoption, &isnull);
+ Assert(!isnull);
+ indcoloptions = (int2vector *) DatumGetPointer(indoptionDatum);
+
+ /* 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,
+ (Datum) indexRelation->rd_options, // This needs to be checked
+ indexRelation->rd_index->indisprimary,
+ false, /* is constraint? */
+ false, /* is deferrable? */
+ false, /* is initially deferred? */
+ false, /* allow table to be a system catalog? */
+ true, /* skip build? */
+ true); /* concurrent? */
+
+ /* Close the relations used and clean up */
+ index_close(indexRelation, RowExclusiveLock);
+ ReleaseSysCache(indexTuple);
+
+ return concurrentOid;
+}
+
+
+/*
+ * index_concurrent_build
+ *
+ * Build index for a concurrent operation. Low-level locks are taken when this
+ * operation is performed.
+ */
+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 */
+ 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_mark
+ *
+ * Update the pg_index row to mark the index with a new status. All the
+ * operations that can be performed on the index marking are listed in
+ * IndexMarkOperation.
+ * When a marking modification is done, the caller needs to commit current
+ * transaction as any new transactions that open the table might perform
+ * read or write operations on the table related.
+ * - INDEX_MARK_READY, index is marked as ready for inserts. When marked as
+ * ready, the index needs to be invalid.
+ * - INDEX_MARK_NOT_READY, index is marked as not ready for inserts. When
+ * marked as not ready, the index needs to be already invalid.
+ * - INDEX_MARK_VALID, index is marked as valid for selects. When marked as
+ * valid, the index needs to be ready.
+ * - INDEX_MARK_NOT_VALID, index is marked as not valid for selects, When
+ * marked as not valid, the index needs to be ready.
+ */
+void
+index_concurrent_mark(Oid indOid, IndexMarkOperation operation)
+{
+ Relation pg_index;
+ HeapTuple indexTuple;
+ Form_pg_index indexForm;
+
+ pg_index = heap_open(IndexRelationId, RowExclusiveLock);
+
+ indexTuple = SearchSysCacheCopy1(INDEXRELID,
+ ObjectIdGetDatum(indOid));
+ if (!HeapTupleIsValid(indexTuple))
+ elog(ERROR, "cache lookup failed for index %u", indOid);
+ indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
+
+ switch(operation)
+ {
+ case INDEX_MARK_READY:
+ Assert(!indexForm->indisready);
+ Assert(!indexForm->indisvalid);
+ indexForm->indisready = true;
+ break;
+
+ case INDEX_MARK_NOT_READY:
+ Assert(indexForm->indisready);
+ Assert(!indexForm->indisvalid);
+ indexForm->indisready = false;
+ break;
+
+ case INDEX_MARK_VALID:
+ Assert(indexForm->indisready);
+ Assert(!indexForm->indisvalid);
+ indexForm->indisvalid = true;
+ break;
+
+ case INDEX_MARK_NOT_VALID:
+ Assert(indexForm->indisready);
+ Assert(indexForm->indisvalid);
+ indexForm->indisvalid = false;
+ break;
+
+ default:
+ /* Do nothing */
+ break;
+ }
+
+ simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
+ CatalogUpdateIndexes(pg_index, indexTuple);
+
+ heap_close(pg_index, RowExclusiveLock);
+}
+
+
+/*
+ * index_concurrent_swap
+ *
+ * Replace old index by old index in a concurrent context. For the time being
+ * what is done here is switching the relation names of the indexes. If extra
+ * operations are necessary during a concurrent swap, processing should be
+ * added here.
+ */
+void
+index_concurrent_swap(Oid newIndexOid, Oid oldIndexOid)
+{
+ char nameNew[NAMEDATALEN],
+ nameOld[NAMEDATALEN],
+ nameTemp[NAMEDATALEN];
+
+ /* The new index is going to use the name of the old index */
+ snprintf(nameNew, NAMEDATALEN, "%s", get_rel_name(newIndexOid));
+ snprintf(nameOld, NAMEDATALEN, "%s", get_rel_name(oldIndexOid));
+
+ /* Change the name of old index to something temporary */
+ snprintf(nameTemp, NAMEDATALEN, "cct_%d", oldIndexOid);
+ RenameRelationInternal(oldIndexOid, nameTemp);
+
+ /* Make the catalog update visible */
+ CommandCounterIncrement();
+
+ /* Change the name of the new index with the old one */
+ RenameRelationInternal(newIndexOid, nameOld);
+
+ /* Make the catalog update visible */
+ CommandCounterIncrement();
+
+ /* Finally change the name of old index with name of the new one */
+ RenameRelationInternal(oldIndexOid, nameNew);
+
+ /* Make the catalog update visible */
+ CommandCounterIncrement();
+}
+
+
+/*
+ * index_concurrent_drop
+ *
+ * Drop a list of indexes in a concurrent process. Deletion has to be done
+ * through performDeletion or dependencies of the index are not dropped.
+ */
+void
+index_concurrent_drop(List *indexIds)
+{
+ ListCell *lc;
+ ObjectAddresses *objects = new_object_addresses();
+
+ Assert(indexIds != NIL);
+
+ /* Scan the list of indexes and build object list */
+ foreach(lc, indexIds)
+ {
+ Oid indexOid = lfirst_oid(lc);
+ ObjectAddress object;
+
+ object.classId = RelationRelationId;
+ object.objectId = indexOid;
+ object.objectSubId = 0;
+
+ /* Add object to list */
+ add_exact_object_address(&object, objects);
+ }
+
+ /* Perform deletion */
+ performMultipleDeletions(objects, DROP_CASCADE, 0);
+}
+
+
/*
* index_constraint_create
*
@@ -2939,6 +3200,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
/*
* Open and lock the parent heap relation. ShareLock is sufficient since
* we only need to be sure no schema or data changes are going on.
+ * In the case of concurrent operation, a lower-level lock is taken to
+ * allow INSERT/UPDATE/DELETE operations.
*/
heapId = IndexGetRelation(indexId, false);
heapRelation = heap_open(heapId, ShareLock);
@@ -2946,6 +3209,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
/*
* Open the target index relation and get an exclusive lock on it, to
* ensure that no one else is touching this particular index.
+ * For concurrent operation, a lower lock is taken to allow INSERT, UPDATE
+ * and DELETE operations.
*/
iRel = index_open(indexId, AccessExclusiveLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index a58101e..d9fc8e4 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -68,12 +68,15 @@ 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);
+ 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,
Oid relId, Oid oldRelId, void *arg);
+static void WaitForVirtualLocks(LOCKTAG heaplocktag);
+static void WaitForOldSnapshots(Snapshot snapshot);
/*
* CheckIndexCompatible
@@ -305,7 +308,6 @@ DefineIndex(IndexStmt *stmt,
Oid tablespaceId;
List *indexColNames;
Relation rel;
- Relation indexRelation;
HeapTuple tuple;
Form_pg_am accessMethodForm;
bool amcanorder;
@@ -314,16 +316,9 @@ DefineIndex(IndexStmt *stmt,
int16 *coloptions;
IndexInfo *indexInfo;
int numberOfAttributes;
- VirtualTransactionId *old_lockholders;
- VirtualTransactionId *old_snapshots;
- int n_old_snapshots;
LockRelId heaprelid;
LOCKTAG heaplocktag;
Snapshot snapshot;
- Relation pg_index;
- HeapTuple indexTuple;
- Form_pg_index indexForm;
- int i;
/*
* count attributes in index
@@ -449,7 +444,8 @@ DefineIndex(IndexStmt *stmt,
indexColNames,
stmt->excludeOpNames,
stmt->primary,
- stmt->isconstraint);
+ stmt->isconstraint,
+ false);
/*
* look up the access method, verify it can handle the requested features
@@ -659,18 +655,8 @@ 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++;
- }
+ WaitForVirtualLocks(heaplocktag);
/*
* At this moment we are sure that there are no transactions with the
@@ -690,50 +676,20 @@ 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);
+ PushActiveSnapshot(GetTransactionSnapshot());
- /* Close both the relations, but keep the locks */
- heap_close(rel, NoLock);
- index_close(indexRelation, NoLock);
+ /* 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.
*/
- pg_index = heap_open(IndexRelationId, RowExclusiveLock);
-
- indexTuple = SearchSysCacheCopy1(INDEXRELID,
- ObjectIdGetDatum(indexRelationId));
- if (!HeapTupleIsValid(indexTuple))
- elog(ERROR, "cache lookup failed for index %u", indexRelationId);
- indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
-
- Assert(!indexForm->indisready);
- Assert(!indexForm->indisvalid);
-
- indexForm->indisready = true;
-
- simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
- CatalogUpdateIndexes(pg_index, indexTuple);
-
- heap_close(pg_index, RowExclusiveLock);
+ index_concurrent_mark(indexRelationId, INDEX_MARK_READY);
/* we can do away with our snapshot */
PopActiveSnapshot();
@@ -750,13 +706,7 @@ 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++;
- }
+ WaitForVirtualLocks(heaplocktag);
/*
* Now take the "reference snapshot" that will be used by validate_index()
@@ -785,105 +735,277 @@ 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.
+ * transactions that might have older snapshots.
+ */
+ WaitForOldSnapshots(snapshot);
+
+ /*
+ * Index can now be marked valid -- update its pg_index entry
+ */
+ index_concurrent_mark(indexRelationId, INDEX_MARK_VALID);
+
+ /*
+ * The pg_index update will cause backends (including this one) to update
+ * relcache entries for the index itself, but we should also send a
+ * relcache inval on the parent table to force replanning of cached plans.
+ * Otherwise existing sessions might fail to use the new index where it
+ * would be useful. (Note that our earlier commits did not create reasons
+ * to replan; relcache flush on the index itself was sufficient.)
+ */
+ CacheInvalidateRelcacheByRelid(heaprelid.relId);
+
+ /* we can now do away with our active snapshot */
+ PopActiveSnapshot();
+
+ /* And we can remove the validating snapshot too */
+ UnregisterSnapshot(snapshot);
+
+ /*
+ * Last thing to do is release the session-level lock on the parent table.
+ */
+ UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+
+ return indexRelationId;
+}
+
+
+/*
+ * ReindexConcurrent
+ *
+ * Process REINDEX CONCURRENTLY for given list of indexes.
+ * Each reindexing step is done simultaneously for all the given
+ * indexes. If no list of indexes is given by the caller, all the
+ * indexes included in the relation will be reindexed.
+ */
+bool
+ReindexConcurrentIndexes(Oid heapOid, List *indexIds)
+{
+ Relation heapRelation;
+ List *concurrentIndexIds = NIL,
+ *indexLocks = NIL,
+ *realIndexIds = indexIds;
+ ListCell *lc, *lc2;
+ LockRelId heapLockId;
+ LOCKTAG heapLocktag;
+ Snapshot snapshot;
+
+ /*
+ * Phase 1 of REINDEX CONCURRENTLY
*
- * 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.)
+ * Here begins the process for rebuilding concurrently the index.
+ * 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.
+ */
+ /* lock level used here should match index lock index_concurrent_create() */
+ heapRelation = heap_open(heapOid, ShareUpdateExclusiveLock);
+
+ /*
+ * If relation has a toast relation, it needs to be reindexed too,
+ * but this cannot be done concurrently.
+ */
+ if (OidIsValid(heapRelation->rd_rel->reltoastrelid))
+ reindex_relation(heapRelation->rd_rel->reltoastrelid,
+ REINDEX_REL_PROCESS_TOAST);
+
+ /* Get the list of indexes from relation if caller has not given anything */
+ if (realIndexIds == NIL)
+ realIndexIds = RelationGetIndexList(heapRelation);
+
+ /* Definetely no indexes, so leave */
+ if (realIndexIds == NIL)
+ {
+ heap_close(heapRelation, NoLock);
+ return false;
+ }
+
+ /* 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")));
+
+ /* Do the concurrent index creation for each index */
+ foreach(lc, realIndexIds)
+ {
+ char *concurrentName;
+ Oid indOid = lfirst_oid(lc);
+ Oid concurrentOid = InvalidOid;
+ Relation indexRel,
+ indexConcurrentRel;
+ LockRelId lockrelid;
+
+ indexRel = index_open(indOid, ShareUpdateExclusiveLock);
+
+ /* Concurrent reindex of index for exclusion constraint is not supported. */
+ if (indexRel->rd_index->indisexclusion)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("concurrent reindex is not supported for exclusion constraints")));
+
+ /* Choose a relation name for concurrent index */
+ concurrentName = ChooseIndexName(get_rel_name(indOid),
+ get_rel_namespace(heapOid),
+ NULL,
+ false,
+ false,
+ false,
+ true);
+
+ /* Create concurrent index based on given index */
+ concurrentOid = index_concurrent_create(heapRelation, 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.
+ */
+ lockrelid = indexRel->rd_lockInfo.lockRelId;
+ indexLocks = lappend(indexLocks, &lockrelid);
+ lockrelid = indexConcurrentRel->rd_lockInfo.lockRelId;
+ indexLocks = lappend(indexLocks, &lockrelid);
+
+ index_close(indexRel, NoLock);
+ index_close(indexConcurrentRel, NoLock);
+ }
+
+ /*
+ * Save the heap lock for following visibility checks with other backends
+ * might conflict with this session.
+ */
+ heapLockId = heapRelation->rd_lockInfo.lockRelId;
+ SET_LOCKTAG_RELATION(heapLocktag, heapLockId.dbId, heapLockId.relId);
+
+ /* 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.
*
- * Also, GetCurrentVirtualXIDs never reports our own vxid, so we need not
- * check for that.
+ * 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.
+ */
+ LockRelationIdForSession(&heapLockId, ShareUpdateExclusiveLock);
+
+ /* Lock each index and each concurrent index accordingly */
+ foreach(lc, indexLocks)
+ {
+ LockRelId lockRel = * (LockRelId *) lfirst(lc);
+ LockRelationIdForSession(&lockRel, ShareUpdateExclusiveLock);
+ }
+
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ StartTransactionCommand();
+
+ /*
+ * Phase 2 of REINDEX CONCURRENTLY
*
- * 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.
+ * We need to wait until no running transactions could have the table open with
+ * the old list of indexes. A concurrent build is done for each concurrent
+ * index that will replace the old indexes. All those indexes share the same
+ * snapshot and they are built in the same transaction.
*/
- old_snapshots = GetCurrentVirtualXIDs(snapshot->xmin, true, false,
- PROC_IS_AUTOVACUUM | PROC_IN_VACUUM,
- &n_old_snapshots);
+ WaitForVirtualLocks(heapLocktag);
- for (i = 0; i < n_old_snapshots; i++)
+ /* Set ActiveSnapshot since functions in the indexes may need it */
+ PushActiveSnapshot(GetTransactionSnapshot());
+
+ /* Get the first element of concurrent index list */
+ lc2 = list_head(concurrentIndexIds);
+
+ foreach(lc, realIndexIds)
{
- if (!VirtualTransactionIdIsValid(old_snapshots[i]))
- continue; /* found uninteresting in previous cycle */
+ Relation indexRel;
+ Oid indOid = lfirst_oid(lc);
+ Oid concurrentOid = lfirst_oid(lc2);
+ bool primary;
- if (i > 0)
- {
- /* see if anything's changed ... */
- VirtualTransactionId *newer_snapshots;
- int n_newer_snapshots;
- int j;
- int k;
+ /* Move to next concurrent item */
+ lc2 = lnext(lc2);
- newer_snapshots = GetCurrentVirtualXIDs(snapshot->xmin,
- 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);
- }
+ /* 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);
- if (VirtualTransactionIdIsValid(old_snapshots[i]))
- VirtualXactLock(old_snapshots[i], true);
+ /* Perform concurrent build of new index */
+ index_concurrent_build(heapOid,
+ 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_concurrent_mark(concurrentOid, INDEX_MARK_READY);
}
+ /* we can do away with our snapshot */
+ PopActiveSnapshot();
+
/*
- * Index can now be marked valid -- update its pg_index entry
+ * Commit this transaction to make the indisready update visible for
+ * concurrent index.
*/
- pg_index = heap_open(IndexRelationId, RowExclusiveLock);
+ CommitTransactionCommand();
+ StartTransactionCommand();
- indexTuple = SearchSysCacheCopy1(INDEXRELID,
- ObjectIdGetDatum(indexRelationId));
- if (!HeapTupleIsValid(indexTuple))
- elog(ERROR, "cache lookup failed for index %u", indexRelationId);
- indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
+ /*
+ * Phase 3 of REINDEX CONCURRENTLY
+ *
+ * During this phase the concurrent indexes catch up with the INSERT that
+ * might have occurred in the parent table and are marked as valid once done.
+ *
+ * We once again wait until no transaction can have the table open with
+ * the index marked as read-only for updates.
+ */
+ WaitForVirtualLocks(heapLocktag);
- Assert(indexForm->indisready);
- Assert(!indexForm->indisvalid);
+ /*
+ * Take the reference snapshot that will be used for the concurrent indexes
+ * validation.
+ */
+ snapshot = RegisterSnapshot(GetTransactionSnapshot());
+ PushActiveSnapshot(snapshot);
- indexForm->indisvalid = true;
+ /*
+ * Perform a scan of each concurrent index with the heap, then insert
+ * any missing index entries.
+ */
+ foreach(lc, concurrentIndexIds)
+ validate_index(heapOid, lfirst_oid(lc), snapshot);
- simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
- CatalogUpdateIndexes(pg_index, indexTuple);
+ /*
+ * Concurrent indexes can now be marked valid -- update pg_index entries
+ */
+ foreach(lc, concurrentIndexIds)
+ index_concurrent_mark(lfirst_oid(lc), INDEX_MARK_VALID);
- heap_close(pg_index, RowExclusiveLock);
+ /*
+ * The concurrent indexes are 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(snapshot);
/*
- * The pg_index update will cause backends (including this one) to update
- * relcache entries for the index itself, but we should also send a
- * relcache inval on the parent table to force replanning of cached plans.
- * Otherwise existing sessions might fail to use the new index where it
- * would be useful. (Note that our earlier commits did not create reasons
- * to replan; relcache flush on the index itself was sufficient.)
+ * The pg_index update will cause backends to update its entries for the
+ * concurrent index but it is necessary to do the same whing
*/
- CacheInvalidateRelcacheByRelid(heaprelid.relId);
+ CacheInvalidateRelcacheByRelid(heapLockId.relId);
/* we can now do away with our active snapshot */
PopActiveSnapshot();
@@ -891,12 +1013,107 @@ DefineIndex(IndexStmt *stmt,
/* And we can remove the validating snapshot too */
UnregisterSnapshot(snapshot);
+ /* Commit this transaction to make the concurrent index valid */
+ CommitTransactionCommand();
+ StartTransactionCommand();
+
/*
- * Last thing to do is release the session-level lock on the parent table.
+ * 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 old
+ * index is marked as invalid once this is done, making it not usable
+ * by other transactions once this transaction is committed.
*/
- UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
- return indexRelationId;
+ /* Take reference snapshot used to wait for older snapshots */
+ snapshot = RegisterSnapshot(GetTransactionSnapshot());
+ PushActiveSnapshot(snapshot);
+
+ /* Wait for old snapshots, like previously */
+ WaitForOldSnapshots(snapshot);
+
+ /* Get the first element is concurrent index list */
+ lc2 = list_head(concurrentIndexIds);
+
+ /* Swap and mark all the indexes involved in the relation */
+ foreach(lc, realIndexIds)
+ {
+ Oid indOid = lfirst_oid(lc);
+ Oid concurrentOid = lfirst_oid(lc2);
+
+ /* Move to next concurrent item */
+ lc2 = lnext(lc2);
+
+ /* Swap old index and its concurrent */
+ index_concurrent_swap(concurrentOid, indOid);
+
+ /* Mark the old index as invalid */
+ index_concurrent_mark(indOid, INDEX_MARK_NOT_VALID);
+ }
+
+ /* We can now do away with our active snapshot */
+ PopActiveSnapshot();
+
+ /* And we can remove the validating snapshot too */
+ UnregisterSnapshot(snapshot);
+
+ /*
+ * Commit this transaction had make old index invalidation visible.
+ */
+ CommitTransactionCommand();
+ StartTransactionCommand();
+
+ /*
+ * Phase 5 of REINDEX CONCURRENTLY
+ *
+ * The old indexes need to be marked as not ready. We need also to wait for
+ * transactions that might use them.
+ */
+ WaitForVirtualLocks(heapLocktag);
+
+ /* Get fresh snapshot for this step */
+ PushActiveSnapshot(GetTransactionSnapshot());
+
+ /* Mark the old indexes as not ready */
+ foreach(lc, realIndexIds)
+ index_concurrent_mark(lfirst_oid(lc), INDEX_MARK_NOT_READY);
+
+ /* We can do away with our snapshot */
+ PopActiveSnapshot();
+
+ /*
+ * Commit this transaction to make the indisready update visible.
+ */
+ CommitTransactionCommand();
+ StartTransactionCommand();
+
+ /* Get fresh snapshot for next step */
+ PushActiveSnapshot(GetTransactionSnapshot());
+
+ /*
+ * Phase 6 of REINDEX CONCURRENTLY
+ *
+ * Drop the old indexes. This needs to be done through performDeletion
+ * or related dependencies will not be dropped for the old indexes.
+ */
+ index_concurrent_drop(realIndexIds);
+
+ /*
+ * Last thing to do is release the session-level lock on the parent table
+ * and the indexes of table.
+ */
+ UnlockRelationIdForSession(&heapLockId, ShareUpdateExclusiveLock);
+ foreach(lc, indexLocks)
+ {
+ LockRelId lockRel = * (LockRelId *) lfirst(lc);
+ UnlockRelationIdForSession(&lockRel, ShareUpdateExclusiveLock);
+ }
+
+ /* We can do away with our snapshot */
+ PopActiveSnapshot();
+
+ return true;
}
@@ -1563,7 +1780,8 @@ ChooseRelationName(const char *name1, const char *name2,
static char *
ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
- bool primary, bool isconstraint)
+ bool primary, bool isconstraint,
+ bool concurrent)
{
char *indexname;
@@ -1589,6 +1807,13 @@ ChooseIndexName(const char *tabname, Oid namespaceId,
"key",
namespaceId);
}
+ else if (concurrent)
+ {
+ indexname = ChooseRelationName(tabname,
+ NULL,
+ "cct",
+ namespaceId);
+ }
else
{
indexname = ChooseRelationName(tabname,
@@ -1701,18 +1926,26 @@ ChooseIndexColumnNames(List *indexElems)
* Recreate a specific index.
*/
void
-ReindexIndex(RangeVar *indexRelation)
+ReindexIndex(RangeVar *indexRelation, bool concurrent)
{
Oid indOid;
Oid heapOid = InvalidOid;
- /* lock level used here should match index lock reindex_index() */
- indOid = RangeVarGetRelidExtended(indexRelation, AccessExclusiveLock,
- false, false,
- RangeVarCallbackForReindexIndex,
- (void *) &heapOid);
+ indOid = RangeVarGetRelidExtended(indexRelation,
+ concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock,
+ false, false,
+ RangeVarCallbackForReindexIndex,
+ (void *) &heapOid);
+
+ /* This is all for the non-concurrent case */
+ if (!concurrent)
+ {
+ reindex_index(indOid, false);
+ return;
+ }
- reindex_index(indOid, false);
+ /* Continue through REINDEX CONCURRENTLY */
+ ReindexConcurrentIndexes(heapOid, list_make1_oid(indOid));
}
/*
@@ -1774,18 +2007,139 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
}
}
+
+/*
+ * WaitForVirtualLocks
+ *
+ * Wait until no transaction can have the table open with the index marked as
+ * read-only for updates.
+ * To do this, inquire which xacts currently would conflict with ShareLock on
+ * the table referred by the LOCKTAG -- ie, which ones have a lock that permits
+ * writing the table. Then wait for each of these xacts to commit or abort.
+ * 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.
+ */
+static void
+WaitForVirtualLocks(LOCKTAG heaplocktag)
+{
+ VirtualTransactionId *old_lockholders;
+
+ old_lockholders = GetLockConflicts(&heaplocktag, ShareLock);
+
+ while (VirtualTransactionIdIsValid(*old_lockholders))
+ {
+ VirtualXactLock(*old_lockholders, true);
+ old_lockholders++;
+ }
+}
+
+
+/*
+ * WaitForOldSnapshots
+ *
+ * Wait for transactions that might have older snapshot than the given one,
+ * because is 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 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.
+ */
+static void
+WaitForOldSnapshots(Snapshot snapshot)
+{
+ int i, n_old_snapshots;
+ VirtualTransactionId *old_snapshots;
+
+ old_snapshots = GetCurrentVirtualXIDs(snapshot->xmin, 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(snapshot->xmin,
+ 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);
+ }
+}
+
+
/*
* ReindexTable
* Recreate all indexes of a table (and of its toast table, if any)
*/
void
-ReindexTable(RangeVar *relation)
+ReindexTable(RangeVar *relation, bool concurrent)
{
Oid heapOid;
/* The lock level used here should match reindex_relation(). */
- heapOid = RangeVarGetRelidExtended(relation, ShareLock, false, false,
- RangeVarCallbackOwnsTable, NULL);
+ heapOid = RangeVarGetRelidExtended(relation,
+ concurrent ? ShareUpdateExclusiveLock : ShareLock,
+ false, false,
+ RangeVarCallbackOwnsTable, NULL);
+
+ /* Run through the concurrent process if necessary */
+ if (concurrent && !ReindexConcurrentIndexes(heapOid, NIL))
+ {
+ ereport(NOTICE,
+ (errmsg("table \"%s\" has no indexes",
+ relation->relname)));
+ return;
+ }
if (!reindex_relation(heapOid, REINDEX_REL_PROCESS_TOAST))
ereport(NOTICE,
@@ -1802,7 +2156,10 @@ ReindexTable(RangeVar *relation)
* That means this must not be called within a user transaction block!
*/
void
-ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
+ReindexDatabase(const char *databaseName,
+ bool do_system,
+ bool do_user,
+ bool concurrent)
{
Relation relationRelation;
HeapScanDesc scan;
@@ -1814,6 +2171,12 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
AssertArg(databaseName);
+ /* CONCURRENTLY operation is not allowed for a database */
+ if (concurrent && do_system)
+ 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),
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 139b1bd..72f0178 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3602,6 +3602,7 @@ _copyReindexStmt(const ReindexStmt *from)
COPY_STRING_FIELD(name);
COPY_SCALAR_FIELD(do_system);
COPY_SCALAR_FIELD(do_user);
+ COPY_SCALAR_FIELD(concurrent);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index cebd030..e178928 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1900,6 +1900,7 @@ _equalReindexStmt(const ReindexStmt *a, const ReindexStmt *b)
COMPARE_STRING_FIELD(name);
COMPARE_SCALAR_FIELD(do_system);
COMPARE_SCALAR_FIELD(do_user);
+ COMPARE_SCALAR_FIELD(concurrent);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0d3a20d..e6ea1ba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -6607,15 +6607,16 @@ opt_if_exists: IF_P EXISTS { $$ = TRUE; }
*****************************************************************************/
ReindexStmt:
- REINDEX reindex_type qualified_name opt_force
+ REINDEX reindex_type qualified_name opt_force opt_concurrently
{
ReindexStmt *n = makeNode(ReindexStmt);
n->kind = $2;
n->relation = $3;
n->name = NULL;
+ n->concurrent = $5;
$$ = (Node *)n;
}
- | REINDEX SYSTEM_P name opt_force
+ | REINDEX SYSTEM_P name opt_force opt_concurrently
{
ReindexStmt *n = makeNode(ReindexStmt);
n->kind = OBJECT_DATABASE;
@@ -6623,9 +6624,10 @@ ReindexStmt:
n->relation = NULL;
n->do_system = true;
n->do_user = false;
+ n->concurrent = $5;
$$ = (Node *)n;
}
- | REINDEX DATABASE name opt_force
+ | REINDEX DATABASE name opt_force opt_concurrently
{
ReindexStmt *n = makeNode(ReindexStmt);
n->kind = OBJECT_DATABASE;
@@ -6633,6 +6635,7 @@ ReindexStmt:
n->relation = NULL;
n->do_system = true;
n->do_user = true;
+ n->concurrent = $5;
$$ = (Node *)n;
}
;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index fde2c82..3bad1b5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1258,15 +1258,19 @@ standard_ProcessUtility(Node *parsetree,
{
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);
+ ReindexIndex(stmt->relation, stmt->concurrent);
break;
case OBJECT_TABLE:
- ReindexTable(stmt->relation);
+ ReindexTable(stmt->relation, stmt->concurrent);
break;
case OBJECT_DATABASE:
@@ -1278,8 +1282,8 @@ standard_ProcessUtility(Node *parsetree,
*/
PreventTransactionChain(isTopLevel,
"REINDEX DATABASE");
- ReindexDatabase(stmt->name,
- stmt->do_system, stmt->do_user);
+ ReindexDatabase(stmt->name, stmt->do_system,
+ stmt->do_user, stmt->concurrent);
break;
default:
elog(ERROR, "unrecognized object type: %d",
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index eb417ce..5410a6c 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -19,6 +19,15 @@
#define DEFAULT_INDEX_TYPE "btree"
+typedef enum IndexMarkOperation
+{
+ INDEX_MARK_VALID,
+ INDEX_MARK_NOT_VALID,
+ INDEX_MARK_READY,
+ INDEX_MARK_NOT_READY
+} IndexMarkOperation;
+
+
/* Typedef for callback function for IndexBuildHeapScan */
typedef void (*IndexBuildCallback) (Relation index,
HeapTuple htup,
@@ -52,6 +61,20 @@ extern Oid index_create(Relation heapRelation,
bool skip_build,
bool concurrent);
+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_mark(Oid indOid, IndexMarkOperation operation);
+
+extern void index_concurrent_swap(Oid indexOid1, Oid indexOid2);
+
+extern void index_concurrent_drop(List *IndexIds);
+
extern void index_constraint_create(Relation heapRelation,
Oid indexRelationId,
IndexInfo *indexInfo,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 300f7ea..7cfe94d 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -26,10 +26,11 @@ extern Oid DefineIndex(IndexStmt *stmt,
bool check_rights,
bool skip_build,
bool quiet);
-extern void ReindexIndex(RangeVar *indexRelation);
-extern void ReindexTable(RangeVar *relation);
+extern void ReindexIndex(RangeVar *indexRelation, bool concurrent);
+extern void ReindexTable(RangeVar *relation, bool concurrent);
extern void ReindexDatabase(const char *databaseName,
- bool do_system, bool do_user);
+ bool do_system, bool do_user, bool concurrent);
+extern bool ReindexConcurrentIndexes(Oid heapOid, List *indexIds);
extern char *makeObjectName(const char *name1, const char *name2,
const char *label);
extern char *ChooseRelationName(const char *name1, const char *name2,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4fe644e..a4000d3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2510,6 +2510,7 @@ typedef struct ReindexStmt
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;
/* ----------------------
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 2ae991e..d64c9b6 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2721,3 +2721,41 @@ ORDER BY thousand;
1 | 1001
(2 rows)
+--
+-- Check behavior of REINDEX and REINDEX CONCURRENTLY
+--
+CREATE TABLE concur_reindex_tab (c1 int, c2 text);
+-- REINDEX
+REINDEX TABLE concur_reindex_tab; -- notice
+REINDEX TABLE concur_reindex_tab CONCURRENTLY; -- notice
+NOTICE: table "concur_reindex_tab" has no indexes
+CREATE INDEX concur_reindex_tab1 ON concur_reindex_tab(c1);
+CREATE INDEX concur_reindex_tab2 ON concur_reindex_tab(c2);
+INSERT INTO concur_reindex_tab VALUES (1,'a');
+INSERT INTO concur_reindex_tab VALUES (2,'a');
+REINDEX INDEX concur_reindex_tab1 CONCURRENTLY;
+REINDEX TABLE concur_reindex_tab CONCURRENTLY;
+-- Check errors
+-- Cannot run inside a transaction block
+BEGIN;
+REINDEX TABLE concur_reindex_tab CONCURRENTLY;
+ERROR: REINDEX CONCURRENTLY cannot run inside a transaction block
+COMMIT;
+REINDEX TABLE pg_database CONCURRENTLY;-- no shared relation
+ERROR: concurrent reindex is not supported for shared relations
+REINDEX DATABASE postgres CONCURRENTLY; -- not allowed for DATABASE
+ERROR: cannot reindex system concurrently
+REINDEX SYSTEM postgres CONCURRENTLY; -- 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 |
+ c2 | text |
+Indexes:
+ "concur_reindex_tab1" btree (c1)
+ "concur_reindex_tab2" btree (c2)
+
+DROP TABLE concur_reindex_tab;
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index 914e7a5..7b3b036 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -912,3 +912,30 @@ ORDER BY thousand;
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, c2 text);
+-- REINDEX
+REINDEX TABLE concur_reindex_tab; -- notice
+REINDEX TABLE concur_reindex_tab CONCURRENTLY; -- notice
+CREATE INDEX concur_reindex_tab1 ON concur_reindex_tab(c1);
+CREATE INDEX concur_reindex_tab2 ON concur_reindex_tab(c2);
+INSERT INTO concur_reindex_tab VALUES (1,'a');
+INSERT INTO concur_reindex_tab VALUES (2,'a');
+REINDEX INDEX concur_reindex_tab1 CONCURRENTLY;
+REINDEX TABLE concur_reindex_tab CONCURRENTLY;
+
+-- Check errors
+-- Cannot run inside a transaction block
+BEGIN;
+REINDEX TABLE concur_reindex_tab CONCURRENTLY;
+COMMIT;
+REINDEX TABLE pg_database CONCURRENTLY;-- no shared relation
+REINDEX DATABASE postgres CONCURRENTLY; -- not allowed for DATABASE
+REINDEX SYSTEM postgres CONCURRENTLY; -- not allowed for SYSTEM
+
+-- Check the relation status, there should not be invalid indexes
+\d concur_reindex_tab
+DROP TABLE concur_reindex_tab;