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;