diff options
Diffstat (limited to 'src/backend/catalog/index.c')
| -rw-r--r-- | src/backend/catalog/index.c | 412 |
1 files changed, 395 insertions, 17 deletions
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 0da209ff21..60a30ce372 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.272 2006/07/31 20:09:00 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.273 2006/08/25 04:06:46 tgl Exp $ * * * INTERFACE ROUTINES @@ -34,6 +34,7 @@ #include "catalog/index.h" #include "catalog/indexing.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_operator.h" #include "catalog/pg_opclass.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -49,8 +50,17 @@ #include "utils/memutils.h" #include "utils/relcache.h" #include "utils/syscache.h" +#include "utils/tuplesort.h" +/* state info for validate_index bulkdelete callback */ +typedef struct +{ + Tuplesortstate *tuplesort; /* for sorting the index TIDs */ + /* statistics (for debug purposes only): */ + double htups, itups, tups_inserted; +} v_i_state; + /* non-export function prototypes */ static TupleDesc ConstructTupleDescriptor(Relation heapRelation, IndexInfo *indexInfo, @@ -61,9 +71,16 @@ static void AppendAttributeTuples(Relation indexRelation, int numatts); static void UpdateIndexRelation(Oid indexoid, Oid heapoid, IndexInfo *indexInfo, Oid *classOids, - bool primary); + bool primary, + bool isvalid); static void index_update_stats(Relation rel, bool hasindex, bool isprimary, Oid reltoastidxid, double reltuples); +static bool validate_index_callback(ItemPointer itemptr, void *opaque); +static void validate_index_heapscan(Relation heapRelation, + Relation indexRelation, + IndexInfo *indexInfo, + Snapshot snapshot, + v_i_state *state); static Oid IndexGetRelation(Oid indexId); @@ -308,6 +325,8 @@ AppendAttributeTuples(Relation indexRelation, int numatts) /* ---------------------------------------------------------------- * UpdateIndexRelation + * + * Construct and insert a new entry in the pg_index catalog * ---------------------------------------------------------------- */ static void @@ -315,7 +334,8 @@ UpdateIndexRelation(Oid indexoid, Oid heapoid, IndexInfo *indexInfo, Oid *classOids, - bool primary) + bool primary, + bool isvalid) { int2vector *indkey; oidvector *indclass; @@ -383,6 +403,7 @@ UpdateIndexRelation(Oid indexoid, values[Anum_pg_index_indisunique - 1] = BoolGetDatum(indexInfo->ii_Unique); values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary); values[Anum_pg_index_indisclustered - 1] = BoolGetDatum(false); + values[Anum_pg_index_indisvalid - 1] = BoolGetDatum(isvalid); values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey); values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass); values[Anum_pg_index_indexprs - 1] = exprsDatum; @@ -427,7 +448,10 @@ UpdateIndexRelation(Oid indexoid, * isconstraint: index is owned by a PRIMARY KEY or UNIQUE constraint * allow_system_table_mods: allow table to be a system catalog * skip_build: true to skip the index_build() step for the moment; caller - * must do it later (typically via reindex_index()) + * must do it later (typically via reindex_index()) + * concurrent: if true, do not lock the table against writers. The index + * will be marked "invalid" and the caller must take additional steps + * to fix it up. * * Returns OID of the created index. */ @@ -443,7 +467,8 @@ index_create(Oid heapRelationId, bool isprimary, bool isconstraint, bool allow_system_table_mods, - bool skip_build) + bool skip_build, + bool concurrent) { Relation pg_class; Relation heapRelation; @@ -456,9 +481,12 @@ index_create(Oid heapRelationId, pg_class = heap_open(RelationRelationId, RowExclusiveLock); /* - * Only SELECT ... FOR UPDATE/SHARE are allowed while doing this + * Only SELECT ... FOR UPDATE/SHARE are allowed while doing a standard + * index build; but for concurrent builds we allow INSERT/UPDATE/DELETE + * (but not VACUUM). */ - heapRelation = heap_open(heapRelationId, ShareLock); + heapRelation = heap_open(heapRelationId, + (concurrent ? ShareUpdateExclusiveLock : ShareLock)); /* * The index will be in the same namespace as its parent table, and is @@ -481,6 +509,16 @@ index_create(Oid heapRelationId, errmsg("user-defined indexes on system catalog tables are not supported"))); /* + * concurrent index build on a system catalog is unsafe because we tend + * to release locks before committing in catalogs + */ + if (concurrent && + IsSystemRelation(heapRelation)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("concurrent index creation on system catalog tables is not supported"))); + + /* * We cannot allow indexing a shared relation after initdb (because * there's no way to make the entry in other databases' pg_class). */ @@ -578,7 +616,7 @@ index_create(Oid heapRelationId, * ---------------- */ UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo, - classObjectId, isprimary); + classObjectId, isprimary, !concurrent); /* * Register constraint and dependencies for the index. @@ -745,9 +783,8 @@ index_create(Oid heapRelationId, } /* - * Close the heap and index; but we keep the ShareLock on the heap and - * the exclusive lock on the index that we acquired above, until end of - * transaction. + * Close the heap and index; but we keep the locks that we acquired above + * until end of transaction. */ index_close(indexRelation, NoLock); heap_close(heapRelation, NoLock); @@ -895,6 +932,7 @@ BuildIndexInfo(Relation index) /* other info */ ii->ii_Unique = indexStruct->indisunique; + ii->ii_Concurrent = false; /* assume normal case */ return ii; } @@ -1327,13 +1365,22 @@ IndexBuildHeapScan(Relation heapRelation, estate); /* - * Ok, begin our scan of the base relation. We use SnapshotAny because we - * must retrieve all tuples and do our own time qual checks. + * Prepare for scan of the base relation. In a normal index build, + * we use SnapshotAny because we must retrieve all tuples and do our own + * time qual checks (because we have to index RECENTLY_DEAD tuples). + * In a concurrent build, we take a regular MVCC snapshot and index + * whatever's live according to that. During bootstrap we just use + * SnapshotNow. */ if (IsBootstrapProcessingMode()) { snapshot = SnapshotNow; - OldestXmin = InvalidTransactionId; + OldestXmin = InvalidTransactionId; /* not used */ + } + else if (indexInfo->ii_Concurrent) + { + snapshot = CopySnapshot(GetTransactionSnapshot()); + OldestXmin = InvalidTransactionId; /* not used */ } else { @@ -1344,8 +1391,8 @@ IndexBuildHeapScan(Relation heapRelation, scan = heap_beginscan(heapRelation, /* relation */ snapshot, /* seeself */ - 0, /* number of keys */ - NULL); /* scan key */ + 0, /* number of keys */ + NULL); /* scan key */ reltuples = 0; @@ -1374,10 +1421,12 @@ IndexBuildHeapScan(Relation heapRelation, scan->rs_cbuf)) { case HEAPTUPLE_DEAD: + /* Definitely dead, we can ignore it */ indexIt = false; tupleIsAlive = false; break; case HEAPTUPLE_LIVE: + /* Normal case, index and unique-check it */ indexIt = true; tupleIsAlive = true; break; @@ -1388,6 +1437,7 @@ IndexBuildHeapScan(Relation heapRelation, * anyway to preserve MVCC semantics. (Pre-existing * transactions could try to use the index after we * finish building it, and may need to see such tuples.) + * Exclude it from unique-checking, however. */ indexIt = true; tupleIsAlive = false; @@ -1500,6 +1550,309 @@ IndexBuildHeapScan(Relation heapRelation, /* + * validate_index - support code for concurrent index builds + * + * We do a concurrent index build by first building the index normally via + * index_create(), while holding a weak lock that allows concurrent + * insert/update/delete. Also, we index only tuples that are valid + * as of the start of the scan (see IndexBuildHeapScan), whereas a normal + * build takes care to include recently-dead tuples. This is OK because + * we won't mark the index valid until all transactions that might be able + * to see those tuples are gone. The reason for doing that is to avoid + * bogus unique-index failures due to concurrent UPDATEs (we might see + * different versions of the same row as being valid when we pass over them, + * if we used HeapTupleSatisfiesVacuum). This leaves us with an index that + * does not contain any tuples added to the table while we built the index. + * + * Next, we commit the transaction so that the index becomes visible to other + * backends, but it is marked not "indisvalid" to prevent the planner from + * relying on it for indexscans. Then we wait for all transactions that + * could have been modifying the table to terminate. At this point we + * know that any subsequently-started transactions will see the index and + * insert their new tuples into it. We then take a new reference snapshot + * which is passed to validate_index(). Any tuples that are valid according + * to this snap, but are not in the index, must be added to the index. + * (Any tuples committed live after the snap will be inserted into the + * index by their originating transaction. Any tuples committed dead before + * the snap need not be indexed, because we will wait out all transactions + * that might care about them before we mark the index valid.) + * + * validate_index() works by first gathering all the TIDs currently in the + * index, using a bulkdelete callback that just stores the TIDs and doesn't + * ever say "delete it". (This should be faster than a plain indexscan; + * also, not all index AMs support full-index indexscan.) Then we sort the + * TIDs, and finally scan the table doing a "merge join" against the TID list + * to see which tuples are missing from the index. Thus we will ensure that + * all tuples valid according to the reference snapshot are in the index. + * + * Building a unique index this way is tricky: we might try to insert a + * tuple that is already dead or is in process of being deleted, and we + * mustn't have a uniqueness failure against an updated version of the same + * row. We can check the tuple to see if it's already dead and tell + * index_insert() not to do the uniqueness check, but that still leaves us + * with a race condition against an in-progress update. To handle that, + * we expect the index AM to recheck liveness of the to-be-inserted tuple + * before it declares a uniqueness error. + * + * After completing validate_index(), we wait until all transactions that + * were alive at the time of the reference snapshot are gone; this is + * necessary to be sure there are none left with a serializable snapshot + * older than the reference (and hence possibly able to see tuples we did + * not index). Then we mark the index valid and commit. + * + * Doing two full table scans is a brute-force strategy. We could try to be + * cleverer, eg storing new tuples in a special area of the table (perhaps + * making the table append-only by setting use_fsm). However that would + * add yet more locking issues. + */ +void +validate_index(Oid heapId, Oid indexId, Snapshot snapshot) +{ + Relation heapRelation, indexRelation; + IndexInfo *indexInfo; + IndexVacuumInfo ivinfo; + v_i_state state; + + /* Open and lock the parent heap relation */ + heapRelation = heap_open(heapId, ShareUpdateExclusiveLock); + /* And the target index relation */ + indexRelation = index_open(indexId, RowExclusiveLock); + + /* + * Fetch info needed for index_insert. (You might think this should + * be passed in from DefineIndex, but its copy is long gone due to + * having been built in a previous transaction.) + */ + indexInfo = BuildIndexInfo(indexRelation); + + /* mark build is concurrent just for consistency */ + indexInfo->ii_Concurrent = true; + + /* + * Scan the index and gather up all the TIDs into a tuplesort object. + */ + ivinfo.index = indexRelation; + ivinfo.vacuum_full = false; + ivinfo.message_level = DEBUG2; + ivinfo.num_heap_tuples = -1; + + state.tuplesort = tuplesort_begin_datum(TIDOID, + TIDLessOperator, + maintenance_work_mem, + false); + state.htups = state.itups = state.tups_inserted = 0; + + (void) index_bulk_delete(&ivinfo, NULL, + validate_index_callback, (void *) &state); + + /* Execute the sort */ + tuplesort_performsort(state.tuplesort); + + /* + * Now scan the heap and "merge" it with the index + */ + validate_index_heapscan(heapRelation, + indexRelation, + indexInfo, + snapshot, + &state); + + /* Done with tuplesort object */ + tuplesort_end(state.tuplesort); + + elog(DEBUG2, + "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples", + state.htups, state.itups, state.tups_inserted); + + /* Close rels, but keep locks */ + index_close(indexRelation, NoLock); + heap_close(heapRelation, NoLock); +} + +/* + * validate_index_callback - bulkdelete callback to collect the index TIDs + */ +static bool +validate_index_callback(ItemPointer itemptr, void *opaque) +{ + v_i_state *state = (v_i_state *) opaque; + + tuplesort_putdatum(state->tuplesort, PointerGetDatum(itemptr), false); + state->itups += 1; + return false; /* never actually delete anything */ +} + +/* + * validate_index_heapscan - second table scan for concurrent index build + * + * This has much code in common with IndexBuildHeapScan, but it's enough + * different that it seems cleaner to have two routines not one. + */ +static void +validate_index_heapscan(Relation heapRelation, + Relation indexRelation, + IndexInfo *indexInfo, + Snapshot snapshot, + v_i_state *state) +{ + HeapScanDesc scan; + HeapTuple heapTuple; + Datum values[INDEX_MAX_KEYS]; + bool isnull[INDEX_MAX_KEYS]; + List *predicate; + TupleTableSlot *slot; + EState *estate; + ExprContext *econtext; + /* state variables for the merge */ + ItemPointer indexcursor = NULL; + bool tuplesort_empty = false; + + /* + * sanity checks + */ + Assert(OidIsValid(indexRelation->rd_rel->relam)); + + /* + * Need an EState for evaluation of index expressions and partial-index + * predicates. Also a slot to hold the current tuple. + */ + estate = CreateExecutorState(); + econtext = GetPerTupleExprContext(estate); + slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation)); + + /* Arrange for econtext's scan tuple to be the tuple under test */ + econtext->ecxt_scantuple = slot; + + /* Set up execution state for predicate, if any. */ + predicate = (List *) + ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, + estate); + + /* + * Prepare for scan of the base relation. We need just those tuples + * satisfying the passed-in reference snapshot. + */ + scan = heap_beginscan(heapRelation, /* relation */ + snapshot, /* seeself */ + 0, /* number of keys */ + NULL); /* scan key */ + + /* + * Scan all tuples matching the snapshot. + */ + while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + ItemPointer heapcursor = &heapTuple->t_self; + + CHECK_FOR_INTERRUPTS(); + + state->htups += 1; + + /* + * "merge" by skipping through the index tuples until we find or + * pass the current heap tuple. + */ + while (!tuplesort_empty && + (!indexcursor || + ItemPointerCompare(indexcursor, heapcursor) < 0)) + { + Datum ts_val; + bool ts_isnull; + + if (indexcursor) + pfree(indexcursor); + tuplesort_empty = !tuplesort_getdatum(state->tuplesort, true, + &ts_val, &ts_isnull); + Assert(tuplesort_empty || !ts_isnull); + indexcursor = (ItemPointer) DatumGetPointer(ts_val); + } + + if (tuplesort_empty || + ItemPointerCompare(indexcursor, heapcursor) > 0) + { + /* + * We've overshot which means this heap tuple is missing from the + * index, so insert it. + */ + bool check_unique; + + MemoryContextReset(econtext->ecxt_per_tuple_memory); + + /* Set up for predicate or expression evaluation */ + ExecStoreTuple(heapTuple, slot, InvalidBuffer, false); + + /* + * In a partial index, discard tuples that don't satisfy the + * predicate. + */ + if (predicate != NIL) + { + if (!ExecQual(predicate, econtext, false)) + continue; + } + + /* + * For the current heap tuple, extract all the attributes we use + * in this index, and note which are null. This also performs + * evaluation of any expressions needed. + */ + FormIndexDatum(indexInfo, + slot, + estate, + values, + isnull); + + /* + * If the tuple is already committed dead, we still have to + * put it in the index (because some xacts might be able to + * see it), but we might as well suppress uniqueness checking. + * This is just an optimization because the index AM is not + * supposed to raise a uniqueness failure anyway. + */ + if (indexInfo->ii_Unique) + { + /* must hold a buffer lock to call HeapTupleSatisfiesNow */ + LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE); + + if (HeapTupleSatisfiesNow(heapTuple->t_data, scan->rs_cbuf)) + check_unique = true; + else + check_unique = false; + + LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); + } + else + check_unique = false; + + /* + * You'd think we should go ahead and build the index tuple here, + * but some index AMs want to do further processing on the data + * first. So pass the values[] and isnull[] arrays, instead. + */ + index_insert(indexRelation, + values, + isnull, + heapcursor, + heapRelation, + check_unique); + + state->tups_inserted += 1; + } + } + + heap_endscan(scan); + + ExecDropSingleTupleTableSlot(slot); + + FreeExecutorState(estate); + + /* These may have been pointing to the now-gone estate */ + indexInfo->ii_ExpressionsState = NIL; + indexInfo->ii_PredicateState = NIL; +} + + +/* * IndexGetRelation: given an index's relation OID, get the OID of the * relation it is an index on. Uses the system cache. */ @@ -1530,9 +1883,12 @@ void reindex_index(Oid indexId) { Relation iRel, - heapRelation; + heapRelation, + pg_index; Oid heapId; bool inplace; + HeapTuple indexTuple; + Form_pg_index indexForm; /* * Open and lock the parent heap relation. ShareLock is sufficient since @@ -1600,6 +1956,28 @@ reindex_index(Oid indexId) PG_END_TRY(); ResetReindexProcessing(); + /* + * If the index is marked invalid (ie, it's from a failed CREATE INDEX + * CONCURRENTLY), we can now mark it valid. This allows REINDEX to be + * used to clean up in such cases. + */ + pg_index = heap_open(IndexRelationId, RowExclusiveLock); + + indexTuple = SearchSysCacheCopy(INDEXRELID, + ObjectIdGetDatum(indexId), + 0, 0, 0); + if (!HeapTupleIsValid(indexTuple)) + elog(ERROR, "cache lookup failed for index %u", indexId); + indexForm = (Form_pg_index) GETSTRUCT(indexTuple); + + if (!indexForm->indisvalid) + { + indexForm->indisvalid = true; + simple_heap_update(pg_index, &indexTuple->t_self, indexTuple); + CatalogUpdateIndexes(pg_index, indexTuple); + } + heap_close(pg_index, RowExclusiveLock); + /* Close rels, but keep locks */ index_close(iRel, NoLock); heap_close(heapRelation, NoLock); |
