1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-07 19:06:32 +03:00

Repair some REINDEX problems per recent discussions. The relcache is

now able to cope with assigning new relfilenode values to nailed-in-cache
indexes, so they can be reindexed using the fully crash-safe method.  This
leaves only shared system indexes as special cases.  Remove the 'index
deactivation' code, since it provides no useful protection in the shared-
index case.  Require reindexing of shared indexes to be done in standalone
mode, but remove other restrictions on REINDEX.  -P (IgnoreSystemIndexes)
now prevents using indexes for lookups, but does not disable index updates.
It is therefore safe to allow from PGOPTIONS.  Upshot: reindexing system catalogs
can be done without a standalone backend for all cases except
shared catalogs.
This commit is contained in:
Tom Lane
2003-09-24 18:54:02 +00:00
parent 5f78c6a886
commit a56a016ceb
22 changed files with 621 additions and 635 deletions

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.216 2003/09/23 01:51:09 inoue Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.217 2003/09/24 18:54:01 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -76,27 +76,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
Oid *classOids,
bool primary);
static Oid IndexGetRelation(Oid indexId);
static bool activate_index(Oid indexId, bool activate, bool inplace);
static bool reindexing = false;
bool
SetReindexProcessing(bool reindexmode)
{
bool old = reindexing;
reindexing = reindexmode;
return old;
}
bool
IsReindexProcessing(void)
{
return reindexing;
}
/*
* ConstructTupleDescriptor
*
@@ -498,8 +479,6 @@ index_create(Oid heapRelationId,
Oid indexoid;
int i;
SetReindexProcessing(false);
/*
* Only SELECT ... FOR UPDATE are allowed while doing this
*/
@@ -973,46 +952,6 @@ FormIndexDatum(IndexInfo *indexInfo,
}
/* ---------------------------------------------
* Indexes of the relation active ?
*
* Caller must hold an adequate lock on the relation to ensure the
* answer won't be changing.
* ---------------------------------------------
*/
bool
IndexesAreActive(Relation heaprel)
{
bool isactive;
Relation indexRelation;
HeapScanDesc scan;
ScanKeyData entry;
if (heaprel->rd_rel->relkind != RELKIND_RELATION &&
heaprel->rd_rel->relkind != RELKIND_TOASTVALUE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("relation \"%s\" isn't an indexable relation",
RelationGetRelationName(heaprel))));
/* If pg_class.relhasindex is set, indexes are active */
isactive = heaprel->rd_rel->relhasindex;
if (isactive)
return isactive;
/* Otherwise, look to see if there are any indexes */
indexRelation = heap_openr(IndexRelationName, AccessShareLock);
ScanKeyEntryInitialize(&entry, 0,
Anum_pg_index_indrelid, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(heaprel)));
scan = heap_beginscan(indexRelation, SnapshotNow, 1, &entry);
if (heap_getnext(scan, ForwardScanDirection) == NULL)
isactive = true; /* no indexes, so report "active" */
heap_endscan(scan);
heap_close(indexRelation, AccessShareLock);
return isactive;
}
/* ----------------
* set relhasindex of relation's pg_class entry
*
@@ -1038,12 +977,13 @@ setRelhasindex(Oid relid, bool hasindex, bool isprimary, Oid reltoastidxid)
HeapScanDesc pg_class_scan = NULL;
/*
* Find the tuple to update in pg_class.
* Find the tuple to update in pg_class. In bootstrap mode we can't
* use heap_update, so cheat and overwrite the tuple in-place. In
* normal processing, make a copy to scribble on.
*/
pg_class = heap_openr(RelationRelationName, RowExclusiveLock);
if (!IsIgnoringSystemIndexes() &&
(!IsReindexProcessing() || pg_class->rd_rel->relhasindex))
if (!IsBootstrapProcessingMode())
{
tuple = SearchSysCacheCopy(RELOID,
ObjectIdGetDatum(relid),
@@ -1064,15 +1004,13 @@ setRelhasindex(Oid relid, bool hasindex, bool isprimary, Oid reltoastidxid)
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for relation %u", relid);
classtuple = (Form_pg_class) GETSTRUCT(tuple);
/* Apply required updates */
/*
* Update fields in the pg_class tuple.
*/
if (pg_class_scan)
LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_EXCLUSIVE);
classtuple = (Form_pg_class) GETSTRUCT(tuple);
if (classtuple->relhasindex != hasindex)
{
classtuple->relhasindex = hasindex;
@@ -1141,80 +1079,48 @@ setNewRelfilenode(Relation relation)
Relation pg_class;
HeapTuple tuple;
Form_pg_class rd_rel;
HeapScanDesc pg_class_scan = NULL;
bool in_place_upd;
RelationData workrel;
Assert(!IsSystemRelation(relation) || IsToastRelation(relation) ||
/* Can't change relfilenode for nailed tables (indexes ok though) */
Assert(!relation->rd_isnailed ||
relation->rd_rel->relkind == RELKIND_INDEX);
/* Can't change for shared tables or indexes */
Assert(!relation->rd_rel->relisshared);
/* Allocate a new relfilenode */
newrelfilenode = newoid();
/*
* Find the RELATION relation tuple for the given relation.
* Find the pg_class tuple for the given relation. This is not used
* during bootstrap, so okay to use heap_update always.
*/
pg_class = heap_openr(RelationRelationName, RowExclusiveLock);
in_place_upd = IsIgnoringSystemIndexes();
if (!in_place_upd)
{
tuple = SearchSysCacheCopy(RELOID,
ObjectIdGetDatum(RelationGetRelid(relation)),
0, 0, 0);
}
else
{
ScanKeyData key[1];
ScanKeyEntryInitialize(&key[0], 0,
ObjectIdAttributeNumber,
F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
pg_class_scan = heap_beginscan(pg_class, SnapshotNow, 1, key);
tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
}
tuple = SearchSysCacheCopy(RELOID,
ObjectIdGetDatum(RelationGetRelid(relation)),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for relation %u",
RelationGetRelid(relation));
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
/* schedule unlinking old relfilenode */
smgrunlink(DEFAULT_SMGR, relation);
/* create another storage file. Is it a little ugly ? */
/* NOTE: any conflict in relfilenode value will be caught here */
memcpy((char *) &workrel, relation, sizeof(RelationData));
workrel.rd_fd = -1;
workrel.rd_node.relNode = newrelfilenode;
heap_storage_create(&workrel);
smgrclose(DEFAULT_SMGR, &workrel);
/* update the pg_class row */
if (in_place_upd)
{
LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_EXCLUSIVE);
rd_rel->relfilenode = newrelfilenode;
LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
WriteNoReleaseBuffer(pg_class_scan->rs_cbuf);
BufferSync();
/* Send out shared cache inval if necessary */
if (!IsBootstrapProcessingMode())
CacheInvalidateHeapTuple(pg_class, tuple);
}
else
{
rd_rel->relfilenode = newrelfilenode;
simple_heap_update(pg_class, &tuple->t_self, tuple);
CatalogUpdateIndexes(pg_class, tuple);
}
/* schedule unlinking old relfilenode */
smgrunlink(DEFAULT_SMGR, relation);
if (!pg_class_scan)
heap_freetuple(tuple);
else
heap_endscan(pg_class_scan);
/* update the pg_class row */
rd_rel->relfilenode = newrelfilenode;
simple_heap_update(pg_class, &tuple->t_self, tuple);
CatalogUpdateIndexes(pg_class, tuple);
heap_freetuple(tuple);
heap_close(pg_class, RowExclusiveLock);
@@ -1264,11 +1170,21 @@ UpdateStats(Oid relid, double reltuples)
whichRel = relation_open(relid, ShareLock);
/*
* Find the RELATION relation tuple for the given relation.
* Find the tuple to update in pg_class. Normally we make a copy of
* the tuple using the syscache, modify it, and apply heap_update.
* But in bootstrap mode we can't use heap_update, so we cheat and
* overwrite the tuple in-place.
*
* We also must cheat if reindexing pg_class itself, because the
* target index may presently not be part of the set of indexes that
* CatalogUpdateIndexes would update (see reindex_relation). In this
* case the stats updates will not be WAL-logged and so could be lost
* in a crash. This seems OK considering VACUUM does the same thing.
*/
pg_class = heap_openr(RelationRelationName, RowExclusiveLock);
in_place_upd = (IsIgnoringSystemIndexes() || IsReindexProcessing());
in_place_upd = IsBootstrapProcessingMode() ||
ReindexIsProcessingHeap(RelationGetRelid(pg_class));
if (!in_place_upd)
{
@@ -1291,6 +1207,7 @@ UpdateStats(Oid relid, double reltuples)
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for relation %u", relid);
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
/*
* Figure values to insert.
@@ -1331,18 +1248,12 @@ UpdateStats(Oid relid, double reltuples)
* also reduces the window wherein concurrent CREATE INDEX commands
* may conflict.)
*/
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
if (rd_rel->relpages != (int32) relpages ||
rd_rel->reltuples != (float4) reltuples)
{
if (in_place_upd)
{
/*
* At bootstrap time, we don't need to worry about concurrency
* or visibility of changes, so we cheat. Also cheat if
* REINDEX.
*/
/* Bootstrap or reindex case: overwrite fields in place. */
LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_EXCLUSIVE);
rd_rel->relpages = (int32) relpages;
rd_rel->reltuples = (float4) reltuples;
@@ -1562,10 +1473,13 @@ IndexBuildHeapScan(Relation heapRelation,
* should not see any tuples inserted by open
* transactions --- unless it's our own transaction.
* (Consider INSERT followed by CREATE INDEX within a
* transaction.)
* transaction.) An exception occurs when reindexing
* a system catalog, because we often release lock on
* system catalogs before committing.
*/
if (!TransactionIdIsCurrentTransactionId(
HeapTupleHeaderGetXmin(heapTuple->t_data)))
HeapTupleHeaderGetXmin(heapTuple->t_data))
&& !IsSystemRelation(heapRelation))
elog(ERROR, "concurrent insert in progress");
indexIt = true;
tupleIsAlive = true;
@@ -1577,10 +1491,13 @@ IndexBuildHeapScan(Relation heapRelation,
* should not see any tuples deleted by open
* transactions --- unless it's our own transaction.
* (Consider DELETE followed by CREATE INDEX within a
* transaction.)
* transaction.) An exception occurs when reindexing
* a system catalog, because we often release lock on
* system catalogs before committing.
*/
if (!TransactionIdIsCurrentTransactionId(
HeapTupleHeaderGetXmax(heapTuple->t_data)))
HeapTupleHeaderGetXmax(heapTuple->t_data))
&& !IsSystemRelation(heapRelation))
elog(ERROR, "concurrent delete in progress");
indexIt = true;
tupleIsAlive = false;
@@ -1690,81 +1607,57 @@ IndexGetRelation(Oid indexId)
return result;
}
/* ---------------------------------
* activate_index -- activate/deactivate the specified index.
* Note that currently PostgreSQL doesn't hold the
* status per index
* ---------------------------------
/*
* reindex_index - This routine is used to recreate a single index
*/
static bool
activate_index(Oid indexId, bool activate, bool inplace)
{
if (!activate) /* Currently does nothing */
return true;
return reindex_index(indexId, false, inplace);
}
/* --------------------------------
* reindex_index - This routine is used to recreate an index
* --------------------------------
*/
bool
reindex_index(Oid indexId, bool force, bool inplace)
void
reindex_index(Oid indexId)
{
Relation iRel,
heapRelation;
IndexInfo *indexInfo;
Oid heapId;
bool old;
bool inplace;
/*
* Open our index relation and get an exclusive lock on it.
*
* Note: doing this before opening the parent heap relation means there's
* a possibility for deadlock failure against another xact that is
* doing normal accesses to the heap and index. However, it's not
* real clear why you'd be needing to do REINDEX on a table that's in
* active use, so I'd rather have the protection of making sure the
* index is locked down.
* Note: for REINDEX INDEX, doing this before opening the parent heap
* relation means there's a possibility for deadlock failure against
* another xact that is doing normal accesses to the heap and index.
* However, it's not real clear why you'd be wanting to do REINDEX INDEX
* on a table that's in active use, so I'd rather have the protection of
* making sure the index is locked down. In the REINDEX TABLE and
* REINDEX DATABASE cases, there is no problem because caller already
* holds exclusive lock on the parent table.
*/
iRel = index_open(indexId);
LockRelation(iRel, AccessExclusiveLock);
old = SetReindexProcessing(true);
/* Get OID of index's parent table */
heapId = iRel->rd_index->indrelid;
/* Open the parent heap relation */
/* Open and lock the parent heap relation */
heapRelation = heap_open(heapId, AccessExclusiveLock);
SetReindexProcessing(heapId, indexId);
/*
* If it's a shared index, we must do inplace processing (because we
* have no way to update relfilenode in other databases). Also, if
* it's a nailed-in-cache index, we must do inplace processing because
* the relcache can't cope with changing its relfilenode.
* have no way to update relfilenode in other databases). Otherwise
* we can do it the normal transaction-safe way.
*
* In either of these cases, we are definitely processing a system index,
* so we'd better be ignoring system indexes.
* Since inplace processing isn't crash-safe, we only allow it in a
* standalone backend. (In the REINDEX TABLE and REINDEX DATABASE cases,
* the caller should have detected this.)
*/
if (iRel->rd_rel->relisshared)
{
if (!IsIgnoringSystemIndexes())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("the target relation %u is shared", indexId)));
inplace = true;
}
#ifndef ENABLE_REINDEX_NAILED_RELATIONS
if (iRel->rd_isnailed)
{
if (!IsIgnoringSystemIndexes())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("the target relation %u is nailed", indexId)));
inplace = true;
}
#endif /* ENABLE_REINDEX_NAILED_RELATIONS */
inplace = iRel->rd_rel->relisshared;
if (inplace && IsUnderPostmaster)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("shared index \"%s\" can only be reindexed in standalone mode",
RelationGetRelationName(iRel))));
/* Fetch info needed for index_build */
indexInfo = BuildIndexInfo(iRel);
@@ -1797,160 +1690,94 @@ reindex_index(Oid indexId, bool force, bool inplace)
* index_build will close both the heap and index relations (but not
* give up the locks we hold on them). So we're done.
*/
SetReindexProcessing(old);
return true;
SetReindexProcessing(InvalidOid, InvalidOid);
}
/*
* ----------------------------
* activate_indexes_of_a_table
* activate/deactivate indexes of the specified table.
* reindex_relation - This routine is used to recreate all indexes
* of a relation (and its toast relation too, if any).
*
* Caller must already hold exclusive lock on the table.
* ----------------------------
* Returns true if any indexes were rebuilt.
*/
bool
activate_indexes_of_a_table(Relation heaprel, bool activate)
reindex_relation(Oid relid)
{
if (IndexesAreActive(heaprel))
{
if (!activate)
setRelhasindex(RelationGetRelid(heaprel), false, false,
InvalidOid);
else
return false;
}
else
{
if (activate)
reindex_relation(RelationGetRelid(heaprel), false);
else
return false;
}
return true;
}
/* --------------------------------
* reindex_relation - This routine is used to recreate indexes
* of a relation.
* --------------------------------
*/
bool
reindex_relation(Oid relid, bool force)
{
Relation indexRelation;
ScanKeyData entry;
HeapScanDesc scan;
HeapTuple indexTuple;
bool old,
reindexed;
bool deactivate_needed,
overwrite;
Relation rel;
overwrite = deactivate_needed = false;
Oid toast_relid;
bool is_pg_class;
bool result;
List *indexIds,
*doneIndexes,
*indexId;
/*
* Ensure to hold an exclusive lock throughout the transaction. The
* lock could be less intensive (in the non-overwrite path) but for
* now it's AccessExclusiveLock for simplicity.
* lock could perhaps be less intensive (in the non-overwrite case)
* but for now it's AccessExclusiveLock for simplicity.
*/
rel = heap_open(relid, AccessExclusiveLock);
/*
* ignore the indexes of the target system relation while processing
* reindex.
*/
if (!IsIgnoringSystemIndexes() &&
IsSystemRelation(rel) && !IsToastRelation(rel))
deactivate_needed = true;
toast_relid = rel->rd_rel->reltoastrelid;
/*
* Shared system indexes must be overwritten because it's impossible
* to update pg_class tuples of all databases.
* Get the list of index OIDs for this relation. (We trust to the
* relcache to get this with a sequential scan if ignoring system
* indexes.)
*/
if (rel->rd_rel->relisshared)
indexIds = RelationGetIndexList(rel);
/*
* reindex_index will attempt to update the pg_class rows for the
* relation and index. If we are processing pg_class itself, we
* want to make sure that the updates do not try to insert index
* entries into indexes we have not processed yet. (When we are
* trying to recover from corrupted indexes, that could easily
* cause a crash.) We can accomplish this because CatalogUpdateIndexes
* will use the relcache's index list to know which indexes to update.
* We just force the index list to be only the stuff we've processed.
*
* It is okay to not insert entries into the indexes we have not
* processed yet because all of this is transaction-safe. If we fail
* partway through, the updated rows are dead and it doesn't matter
* whether they have index entries. Also, a new pg_class index will
* be created with an entry for its own pg_class row because we do
* setNewRelfilenode() before we do index_build().
*/
is_pg_class = (RelationGetRelid(rel) == RelOid_pg_class);
doneIndexes = NIL;
/* Reindex all the indexes. */
foreach(indexId, indexIds)
{
if (IsIgnoringSystemIndexes())
{
overwrite = true;
deactivate_needed = true;
}
else
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("the target relation %u is shared", relid)));
Oid indexOid = lfirsto(indexId);
if (is_pg_class)
RelationSetIndexList(rel, doneIndexes);
reindex_index(indexOid);
CommandCounterIncrement();
if (is_pg_class)
doneIndexes = lappendo(doneIndexes, indexOid);
}
old = SetReindexProcessing(true);
if (deactivate_needed)
{
if (IndexesAreActive(rel))
{
if (!force)
{
SetReindexProcessing(old);
heap_close(rel, NoLock);
return false;
}
activate_indexes_of_a_table(rel, false);
CommandCounterIncrement();
}
}
if (is_pg_class)
RelationSetIndexList(rel, indexIds);
/*
* Continue to hold the lock.
* Close rel, but continue to hold the lock.
*/
heap_close(rel, NoLock);
indexRelation = heap_openr(IndexRelationName, AccessShareLock);
ScanKeyEntryInitialize(&entry, 0, Anum_pg_index_indrelid,
F_OIDEQ, ObjectIdGetDatum(relid));
scan = heap_beginscan(indexRelation, SnapshotNow, 1, &entry);
reindexed = false;
while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
result = (indexIds != NIL);
if (activate_index(index->indexrelid, true, overwrite))
reindexed = true;
else
{
reindexed = false;
break;
}
}
heap_endscan(scan);
heap_close(indexRelation, AccessShareLock);
if (reindexed)
{
/*
* Ok,we could use the reindexed indexes of the target system
* relation now.
*/
if (deactivate_needed)
{
if (!overwrite && relid == RelOid_pg_class)
{
/*
* For pg_class, relhasindex should be set to true here in
* place.
*/
setRelhasindex(relid, true, false, InvalidOid);
CommandCounterIncrement();
/*
* If the relation has a secondary toast rel, reindex that too while we
* still hold the lock on the master table.
*/
if (toast_relid != InvalidOid)
result |= reindex_relation(toast_relid);
/*
* However the following setRelhasindex() is needed to
* keep consistency with WAL.
*/
}
setRelhasindex(relid, true, false, InvalidOid);
}
}
SetReindexProcessing(old);
return reindexed;
return result;
}