Get rid of cluster.c's apparatus for rebuilding a relation's indexes

in favor of using the REINDEX TABLE apparatus, which does the same thing
simpler and faster.  Also, make TRUNCATE not use cluster.c at all, but
just assign a new relfilenode and REINDEX.  This partially addresses
Hartmut Raschick's complaint from last December that 7.4's TRUNCATE is
an order of magnitude slower than prior releases.  By getting rid of
a lot of unnecessary catalog updates, these changes buy back about a
factor of two (on my system).  The remaining overhead seems associated
with creating and deleting storage files, which we may not be able to
do much about without abandoning transaction safety for TRUNCATE.
This commit is contained in:
Tom Lane 2004-05-08 00:34:49 +00:00
parent 7c6baade7b
commit dd16b7aa9e
6 changed files with 161 additions and 262 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.229 2004/05/05 04:48:45 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.230 2004/05/08 00:34:49 tgl Exp $
*
*
* INTERFACE ROUTINES
@ -1729,12 +1729,13 @@ reindex_index(Oid indexId)
/*
* reindex_relation - This routine is used to recreate all indexes
* of a relation (and its toast relation too, if any).
* of a relation (and optionally its toast relation too, if any).
*
* Returns true if any indexes were rebuilt.
* Returns true if any indexes were rebuilt. Note that a
* CommandCounterIncrement will occur after each index rebuild.
*/
bool
reindex_relation(Oid relid)
reindex_relation(Oid relid, bool toast_too)
{
Relation rel;
Oid toast_relid;
@ -1810,8 +1811,8 @@ reindex_relation(Oid relid)
* 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);
if (toast_too && OidIsValid(toast_relid))
result |= reindex_relation(toast_relid, false);
return result;
}

View File

@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.122 2004/05/06 16:10:57 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.123 2004/05/08 00:34:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -37,20 +37,6 @@
#include "utils/relcache.h"
/*
* We need one of these structs for each index in the relation to be
* clustered. It's basically the data needed by index_create() so
* we can rebuild the indexes on the new heap.
*/
typedef struct
{
Oid indexOID;
char *indexName;
IndexInfo *indexInfo;
Oid accessMethodOID;
Oid *classOID;
} IndexAttrs;
/*
* This struct is used to pass around the information on tables to be
* clustered. We need this so we can make a list of them when invoked without
@ -64,6 +50,7 @@ typedef struct
static void cluster_rel(RelToCluster *rv, bool recheck);
static void rebuild_relation(Relation OldHeap, Oid indexOid);
static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex);
static List *get_tables_to_cluster(MemoryContext cluster_context);
@ -411,30 +398,99 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid)
}
/*
* rebuild_relation: rebuild an existing relation
* mark_index_clustered: mark the specified index as the one clustered on
*
* This is shared code between CLUSTER and TRUNCATE. In the TRUNCATE
* case, the new relation is built and left empty. In the CLUSTER case,
* it is filled with data read from the old relation in the order specified
* by the index.
* With indexOid == InvalidOid, will mark all indexes of rel not-clustered.
*/
void
mark_index_clustered(Relation rel, Oid indexOid)
{
HeapTuple indexTuple;
Form_pg_index indexForm;
Relation pg_index;
List *index;
/*
* If the index is already marked clustered, no need to do anything.
*/
if (OidIsValid(indexOid))
{
indexTuple = SearchSysCache(INDEXRELID,
ObjectIdGetDatum(indexOid),
0, 0, 0);
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexOid);
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
if (indexForm->indisclustered)
{
ReleaseSysCache(indexTuple);
return;
}
ReleaseSysCache(indexTuple);
}
/*
* Check each index of the relation and set/clear the bit as needed.
*/
pg_index = heap_openr(IndexRelationName, RowExclusiveLock);
foreach(index, RelationGetIndexList(rel))
{
Oid thisIndexOid = lfirsto(index);
indexTuple = SearchSysCacheCopy(INDEXRELID,
ObjectIdGetDatum(thisIndexOid),
0, 0, 0);
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
/*
* Unset the bit if set. We know it's wrong because we checked
* this earlier.
*/
if (indexForm->indisclustered)
{
indexForm->indisclustered = false;
simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
CatalogUpdateIndexes(pg_index, indexTuple);
/* Ensure we see the update in the index's relcache entry */
CacheInvalidateRelcacheByRelid(thisIndexOid);
}
else if (thisIndexOid == indexOid)
{
indexForm->indisclustered = true;
simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
CatalogUpdateIndexes(pg_index, indexTuple);
/* Ensure we see the update in the index's relcache entry */
CacheInvalidateRelcacheByRelid(thisIndexOid);
}
heap_freetuple(indexTuple);
}
heap_close(pg_index, RowExclusiveLock);
}
/*
* rebuild_relation: rebuild an existing relation in index order
*
* OldHeap: table to rebuild --- must be opened and exclusive-locked!
* indexOid: index to cluster by, or InvalidOid in TRUNCATE case
* indexOid: index to cluster by
*
* NB: this routine closes OldHeap at the right time; caller should not.
*/
void
static void
rebuild_relation(Relation OldHeap, Oid indexOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid oldClusterIndex;
List *indexes;
Oid OIDNewHeap;
char NewHeapName[NAMEDATALEN];
ObjectAddress object;
/* Save the information about all indexes on the relation. */
indexes = get_indexattr_list(OldHeap, &oldClusterIndex);
/* Mark the correct index as clustered */
mark_index_clustered(OldHeap, indexOid);
/* Close relcache entry, but keep lock until transaction commit */
heap_close(OldHeap, NoLock);
@ -459,8 +515,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid)
/*
* Copy the heap data into the new table in the desired order.
*/
if (OidIsValid(indexOid))
copy_heap_data(OIDNewHeap, tableOid, indexOid);
copy_heap_data(OIDNewHeap, tableOid, indexOid);
/* To make the new heap's data visible (probably not needed?). */
CommandCounterIncrement();
@ -484,11 +539,11 @@ rebuild_relation(Relation OldHeap, Oid indexOid)
/* performDeletion does CommandCounterIncrement at end */
/*
* Recreate each index on the relation. We do not need
* CommandCounterIncrement() because rebuild_indexes does it.
* Rebuild each index on the relation (but not the toast table,
* which is all-new at this point). We do not need
* CommandCounterIncrement() because reindex_relation does it.
*/
rebuild_indexes(tableOid, indexes,
(OidIsValid(indexOid) ? indexOid : oldClusterIndex));
reindex_relation(tableOid, false);
}
/*
@ -589,138 +644,6 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
heap_close(NewHeap, NoLock);
}
/*
* Get the necessary info about the indexes of the relation and
* return a list of IndexAttrs structures. Also, *OldClusterIndex
* is set to the OID of the existing clustered index, or InvalidOid
* if there is none.
*/
List *
get_indexattr_list(Relation OldHeap, Oid *OldClusterIndex)
{
List *indexes = NIL;
List *indlist;
*OldClusterIndex = InvalidOid;
/* Ask the relcache to produce a list of the indexes of the old rel */
foreach(indlist, RelationGetIndexList(OldHeap))
{
Oid indexOID = lfirsto(indlist);
Relation oldIndex;
IndexAttrs *attrs;
oldIndex = index_open(indexOID);
attrs = (IndexAttrs *) palloc(sizeof(IndexAttrs));
attrs->indexOID = indexOID;
attrs->indexName = pstrdup(NameStr(oldIndex->rd_rel->relname));
attrs->accessMethodOID = oldIndex->rd_rel->relam;
attrs->indexInfo = BuildIndexInfo(oldIndex);
attrs->classOID = (Oid *)
palloc(sizeof(Oid) * attrs->indexInfo->ii_NumIndexAttrs);
memcpy(attrs->classOID, oldIndex->rd_index->indclass,
sizeof(Oid) * attrs->indexInfo->ii_NumIndexAttrs);
if (oldIndex->rd_index->indisclustered)
*OldClusterIndex = indexOID;
index_close(oldIndex);
indexes = lappend(indexes, attrs);
}
return indexes;
}
/*
* Create new indexes and swap the filenodes with old indexes. Then drop
* the new index (carrying the old index filenode along).
*
* OIDClusterIndex is the OID of the index to be marked as clustered, or
* InvalidOid if none should be marked clustered.
*/
void
rebuild_indexes(Oid OIDOldHeap, List *indexes, Oid OIDClusterIndex)
{
List *elem;
foreach(elem, indexes)
{
IndexAttrs *attrs = (IndexAttrs *) lfirst(elem);
Oid oldIndexOID = attrs->indexOID;
Oid newIndexOID;
char newIndexName[NAMEDATALEN];
bool isclustered;
ObjectAddress object;
Form_pg_index index;
HeapTuple tuple;
Relation pg_index;
/* Create the new index under a temporary name */
snprintf(newIndexName, sizeof(newIndexName),
"pg_temp_%u", oldIndexOID);
/*
* The new index will have primary and constraint status set to
* false, but since we will only use its filenode it doesn't
* matter: after the filenode swap the index will keep the
* constraint status of the old index.
*/
newIndexOID = index_create(OIDOldHeap,
newIndexName,
attrs->indexInfo,
attrs->accessMethodOID,
attrs->classOID,
false,
false,
allowSystemTableMods,
false);
CommandCounterIncrement();
/* Swap the filenodes. */
swap_relfilenodes(oldIndexOID, newIndexOID);
CommandCounterIncrement();
/*
* Make sure that indisclustered is correct: it should be set only
* for the index specified by the caller.
*/
isclustered = (oldIndexOID == OIDClusterIndex);
pg_index = heap_openr(IndexRelationName, RowExclusiveLock);
tuple = SearchSysCacheCopy(INDEXRELID,
ObjectIdGetDatum(oldIndexOID),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for index %u", oldIndexOID);
index = (Form_pg_index) GETSTRUCT(tuple);
if (index->indisclustered != isclustered)
{
index->indisclustered = isclustered;
simple_heap_update(pg_index, &tuple->t_self, tuple);
CatalogUpdateIndexes(pg_index, tuple);
/* Ensure we see the update in the index's relcache entry */
CacheInvalidateRelcacheByRelid(oldIndexOID);
}
heap_freetuple(tuple);
heap_close(pg_index, RowExclusiveLock);
/* Destroy new index with old filenode */
object.classId = RelOid_pg_class;
object.objectId = newIndexOID;
object.objectSubId = 0;
/*
* The relation is local to our transaction and we know nothing
* depends on it, so DROP_RESTRICT should be OK.
*/
performDeletion(&object, DROP_RESTRICT);
/* performDeletion does CommandCounterIncrement() at its end */
}
}
/*
* Swap the relfilenodes for two given relations.
*

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.118 2004/05/05 04:48:45 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.119 2004/05/08 00:34:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -826,7 +826,7 @@ ReindexTable(RangeVar *relation, bool force /* currently unused */ )
ReleaseSysCache(tuple);
if (!reindex_relation(heapOid))
if (!reindex_relation(heapOid, true))
ereport(NOTICE,
(errmsg("table \"%s\" has no indexes",
relation->relname)));
@ -936,7 +936,7 @@ ReindexDatabase(const char *dbname, bool force /* currently unused */,
StartTransactionCommand();
SetQuerySnapshot(); /* might be needed for functions in
* indexes */
if (reindex_relation(relid))
if (reindex_relation(relid, true))
ereport(NOTICE,
(errmsg("table \"%s\" was reindexed",
get_rel_name(relid))));

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.105 2004/05/07 00:24:57 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.106 2004/05/08 00:34:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -490,14 +490,13 @@ RemoveRelation(const RangeVar *relation, DropBehavior behavior)
/*
* TruncateRelation
* Removes all the rows from a relation.
*
* Note: This routine only does safety and permissions checks;
* rebuild_relation in cluster.c does the actual work.
*/
void
TruncateRelation(const RangeVar *relation)
{
Relation rel;
Oid heap_relid;
Oid toast_relid;
/* Grab exclusive lock in preparation for truncate */
rel = heap_openrv(relation, AccessExclusiveLock);
@ -520,6 +519,16 @@ TruncateRelation(const RangeVar *relation)
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel))));
/*
* We can never allow truncation of shared or nailed-in-cache relations,
* because we can't support changing their relfilenode values.
*/
if (rel->rd_rel->relisshared || rel->rd_isnailed)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot truncate system relation \"%s\"",
RelationGetRelationName(rel))));
/*
* Don't allow truncate on temp tables of other backends ... their
* local buffer manager is not going to cope.
@ -535,17 +544,31 @@ TruncateRelation(const RangeVar *relation)
heap_truncate_check_FKs(rel);
/*
* Do the real work using the same technique as cluster, but without
* the data-copying portion
* Okay, here we go: create a new empty storage file for the relation,
* and assign it as the relfilenode value. The old storage file is
* scheduled for deletion at commit.
*/
rebuild_relation(rel, InvalidOid);
setNewRelfilenode(rel);
/* NB: rebuild_relation does heap_close() */
heap_relid = RelationGetRelid(rel);
toast_relid = rel->rd_rel->reltoastrelid;
heap_close(rel, NoLock);
/*
* You might think we need to truncate the rel's toast table here too,
* but actually we don't; it will have been rebuilt in an empty state.
* The same for the toast table, if any.
*/
if (OidIsValid(toast_relid))
{
rel = relation_open(toast_relid, AccessExclusiveLock);
setNewRelfilenode(rel);
heap_close(rel, NoLock);
}
/*
* Reconstruct the indexes to match, and we're done.
*/
reindex_relation(heap_relid, true);
}
/*----------
@ -2098,14 +2121,31 @@ ATRewriteTables(List **wqueue)
/* Build a temporary relation and copy data */
Oid OIDNewHeap;
char NewHeapName[NAMEDATALEN];
List *indexes;
Oid oldClusterIndex;
Relation OldHeap;
ObjectAddress object;
/* Save the information about all indexes on the relation. */
OldHeap = heap_open(tab->relid, NoLock);
indexes = get_indexattr_list(OldHeap, &oldClusterIndex);
/*
* We can never allow rewriting of shared or nailed-in-cache
* relations, because we can't support changing their relfilenode
* values.
*/
if (OldHeap->rd_rel->relisshared || OldHeap->rd_isnailed)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite system relation \"%s\"",
RelationGetRelationName(OldHeap))));
/*
* Don't allow rewrite on temp tables of other backends ... their
* local buffer manager is not going to cope.
*/
if (isOtherTempNamespace(RelationGetNamespace(OldHeap)))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite temporary tables of other sessions")));
heap_close(OldHeap, NoLock);
/*
@ -2146,10 +2186,11 @@ ATRewriteTables(List **wqueue)
/* performDeletion does CommandCounterIncrement at end */
/*
* Rebuild each index on the relation. We do not need
* CommandCounterIncrement() because rebuild_indexes does it.
* Rebuild each index on the relation (but not the toast table,
* which is all-new anyway). We do not need
* CommandCounterIncrement() because reindex_relation does it.
*/
rebuild_indexes(tab->relid, indexes, oldClusterIndex);
reindex_relation(tab->relid, false);
}
else
{
@ -4991,11 +5032,7 @@ ATExecChangeOwner(Oid relationOid, int32 newOwnerSysId)
static void
ATExecClusterOn(Relation rel, const char *indexName)
{
Relation pg_index;
List *index;
Oid indexOid;
HeapTuple indexTuple;
Form_pg_index indexForm;
indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace);
@ -5008,67 +5045,8 @@ ATExecClusterOn(Relation rel, const char *indexName)
/* Check index is valid to cluster on */
check_index_is_clusterable(rel, indexOid);
indexTuple = SearchSysCache(INDEXRELID,
ObjectIdGetDatum(indexOid),
0, 0, 0);
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexOid);
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
/*
* If this is the same index the relation was previously clustered on,
* no need to do anything.
*/
if (indexForm->indisclustered)
{
ReleaseSysCache(indexTuple);
return;
}
ReleaseSysCache(indexTuple);
pg_index = heap_openr(IndexRelationName, RowExclusiveLock);
/*
* Now check each index in the relation and set the bit where needed.
*/
foreach(index, RelationGetIndexList(rel))
{
HeapTuple idxtuple;
Form_pg_index idxForm;
indexOid = lfirsto(index);
idxtuple = SearchSysCacheCopy(INDEXRELID,
ObjectIdGetDatum(indexOid),
0, 0, 0);
if (!HeapTupleIsValid(idxtuple))
elog(ERROR, "cache lookup failed for index %u", indexOid);
idxForm = (Form_pg_index) GETSTRUCT(idxtuple);
/*
* Unset the bit if set. We know it's wrong because we checked
* this earlier.
*/
if (idxForm->indisclustered)
{
idxForm->indisclustered = false;
simple_heap_update(pg_index, &idxtuple->t_self, idxtuple);
CatalogUpdateIndexes(pg_index, idxtuple);
/* Ensure we see the update in the index's relcache entry */
CacheInvalidateRelcacheByRelid(indexOid);
}
else if (idxForm->indexrelid == indexForm->indexrelid)
{
idxForm->indisclustered = true;
simple_heap_update(pg_index, &idxtuple->t_self, idxtuple);
CatalogUpdateIndexes(pg_index, idxtuple);
/* Ensure we see the update in the index's relcache entry */
CacheInvalidateRelcacheByRelid(indexOid);
}
heap_freetuple(idxtuple);
}
heap_close(pg_index, RowExclusiveLock);
/* And do the work */
mark_index_clustered(rel, indexOid);
}
/*

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.55 2004/05/05 04:48:47 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.56 2004/05/08 00:34:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -68,6 +68,6 @@ extern double IndexBuildHeapScan(Relation heapRelation,
void *callback_state);
extern void reindex_index(Oid indexId);
extern bool reindex_relation(Oid relid);
extern bool reindex_relation(Oid relid, bool toast_too);
#endif /* INDEX_H */

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.22 2004/05/06 16:10:57 tgl Exp $
* $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.23 2004/05/08 00:34:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -20,11 +20,8 @@
extern void cluster(ClusterStmt *stmt);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid);
extern void rebuild_relation(Relation OldHeap, Oid indexOid);
extern void mark_index_clustered(Relation rel, Oid indexOid);
extern Oid make_new_heap(Oid OIDOldHeap, const char *NewName);
extern List *get_indexattr_list(Relation OldHeap, Oid *OldClusterIndex);
extern void rebuild_indexes(Oid OIDOldHeap, List *indexes,
Oid OIDClusterIndex);
extern void swap_relfilenodes(Oid r1, Oid r2);
#endif /* CLUSTER_H */