diff options
Diffstat (limited to 'src/backend/commands')
-rw-r--r-- | src/backend/commands/analyze.c | 11 | ||||
-rw-r--r-- | src/backend/commands/dbcommands.c | 28 | ||||
-rw-r--r-- | src/backend/commands/vacuum.c | 380 | ||||
-rw-r--r-- | src/backend/commands/vacuumlazy.c | 65 |
4 files changed, 323 insertions, 161 deletions
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index f288cf10e3e..35fc293de7f 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.93 2006/03/23 00:19:28 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.94 2006/07/10 16:20:50 alvherre Exp $ * *------------------------------------------------------------------------- */ @@ -424,8 +424,9 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt) { vac_update_relstats(RelationGetRelid(onerel), RelationGetNumberOfBlocks(onerel), - totalrows, - hasindex); + totalrows, hasindex, + InvalidTransactionId, InvalidTransactionId); + for (ind = 0; ind < nindexes; ind++) { AnlIndexData *thisdata = &indexdata[ind]; @@ -434,8 +435,8 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt) totalindexrows = ceil(thisdata->tupleFract * totalrows); vac_update_relstats(RelationGetRelid(Irel[ind]), RelationGetNumberOfBlocks(Irel[ind]), - totalindexrows, - false); + totalindexrows, false, + InvalidTransactionId, InvalidTransactionId); } /* report results to the stats collector, too */ diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index 518fed81968..6d744a5bad3 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.181 2006/05/04 16:07:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.182 2006/07/10 16:20:50 alvherre Exp $ * *------------------------------------------------------------------------- */ @@ -55,7 +55,7 @@ static bool get_db_info(const char *name, LOCKMODE lockmode, Oid *dbIdP, Oid *ownerIdP, int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP, Oid *dbLastSysOidP, - TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP, + TransactionId *dbVacuumXidP, TransactionId *dbMinXidP, Oid *dbTablespace); static bool have_createdb_privilege(void); static void remove_dbtablespaces(Oid db_id); @@ -76,7 +76,7 @@ createdb(const CreatedbStmt *stmt) bool src_allowconn; Oid src_lastsysoid; TransactionId src_vacuumxid; - TransactionId src_frozenxid; + TransactionId src_minxid; Oid src_deftablespace; volatile Oid dst_deftablespace; Relation pg_database_rel; @@ -228,7 +228,7 @@ createdb(const CreatedbStmt *stmt) if (!get_db_info(dbtemplate, ShareLock, &src_dboid, &src_owner, &src_encoding, &src_istemplate, &src_allowconn, &src_lastsysoid, - &src_vacuumxid, &src_frozenxid, &src_deftablespace)) + &src_vacuumxid, &src_minxid, &src_deftablespace)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("template database \"%s\" does not exist", @@ -327,16 +327,6 @@ createdb(const CreatedbStmt *stmt) } /* - * Normally we mark the new database with the same datvacuumxid and - * datfrozenxid as the source. However, if the source is not allowing - * connections then we assume it is fully frozen, and we can set the - * current transaction ID as the xid limits. This avoids immediately - * starting to generate warnings after cloning template0. - */ - if (!src_allowconn) - src_vacuumxid = src_frozenxid = GetCurrentTransactionId(); - - /* * Check for db name conflict. This is just to give a more friendly * error message than "unique index violation". There's a race condition * but we're willing to accept the less friendly message in that case. @@ -367,7 +357,7 @@ createdb(const CreatedbStmt *stmt) new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit); new_record[Anum_pg_database_datlastsysoid - 1] = ObjectIdGetDatum(src_lastsysoid); new_record[Anum_pg_database_datvacuumxid - 1] = TransactionIdGetDatum(src_vacuumxid); - new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid); + new_record[Anum_pg_database_datminxid - 1] = TransactionIdGetDatum(src_minxid); new_record[Anum_pg_database_dattablespace - 1] = ObjectIdGetDatum(dst_deftablespace); /* @@ -1066,7 +1056,7 @@ get_db_info(const char *name, LOCKMODE lockmode, Oid *dbIdP, Oid *ownerIdP, int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP, Oid *dbLastSysOidP, - TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP, + TransactionId *dbVacuumXidP, TransactionId *dbMinXidP, Oid *dbTablespace) { bool result = false; @@ -1155,9 +1145,9 @@ get_db_info(const char *name, LOCKMODE lockmode, /* limit of vacuumed XIDs */ if (dbVacuumXidP) *dbVacuumXidP = dbform->datvacuumxid; - /* limit of frozen XIDs */ - if (dbFrozenXidP) - *dbFrozenXidP = dbform->datfrozenxid; + /* limit of min XIDs */ + if (dbMinXidP) + *dbMinXidP = dbform->datminxid; /* default tablespace for this database */ if (dbTablespace) *dbTablespace = dbform->dattablespace; diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 17a802dc30e..1c66bf98f12 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.332 2006/07/03 22:45:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.333 2006/07/10 16:20:50 alvherre Exp $ * *------------------------------------------------------------------------- */ @@ -25,6 +25,7 @@ #include "access/clog.h" #include "access/genam.h" #include "access/heapam.h" +#include "access/multixact.h" #include "access/subtrans.h" #include "access/xlog.h" #include "catalog/catalog.h" @@ -38,6 +39,7 @@ #include "miscadmin.h" #include "postmaster/autovacuum.h" #include "storage/freespace.h" +#include "storage/pmsignal.h" #include "storage/procarray.h" #include "storage/smgr.h" #include "tcop/pquery.h" @@ -127,6 +129,7 @@ typedef struct VRelStats Size min_tlen; Size max_tlen; bool hasindex; + TransactionId minxid; /* Minimum Xid present anywhere on table */ /* vtlinks array for tuple chain following - sorted by new_tid */ int num_vtlinks; VTupleLink vtlinks; @@ -194,25 +197,22 @@ static MemoryContext vac_context = NULL; static int elevel = -1; -static TransactionId OldestXmin; -static TransactionId FreezeLimit; - /* non-export function prototypes */ static List *get_rel_oids(List *relids, const RangeVar *vacrel, const char *stmttype); -static void vac_update_dbstats(Oid dbid, - TransactionId vacuumXID, - TransactionId frozenXID); -static void vac_truncate_clog(TransactionId vacuumXID, - TransactionId frozenXID); -static bool vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind); +static void vac_update_dbminxid(Oid dbid, + TransactionId *minxid, + TransactionId *vacuumxid); +static void vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid); +static void vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind); static void full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt); static void scan_heap(VRelStats *vacrelstats, Relation onerel, - VacPageList vacuum_pages, VacPageList fraged_pages); + VacPageList vacuum_pages, VacPageList fraged_pages, + TransactionId FreezeLimit, TransactionId OldestXmin); static void repair_frag(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages, VacPageList fraged_pages, - int nindexes, Relation *Irel); + int nindexes, Relation *Irel, TransactionId OldestXmin); static void move_chain_tuple(Relation rel, Buffer old_buf, Page old_page, HeapTuple old_tup, Buffer dst_buf, Page dst_page, VacPage dst_vacpage, @@ -269,8 +269,6 @@ void vacuum(VacuumStmt *vacstmt, List *relids) { const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE"; - TransactionId initialOldestXmin = InvalidTransactionId; - TransactionId initialFreezeLimit = InvalidTransactionId; volatile MemoryContext anl_context = NULL; volatile bool all_rels, in_outer_xact, @@ -353,32 +351,6 @@ vacuum(VacuumStmt *vacstmt, List *relids) */ relations = get_rel_oids(relids, vacstmt->relation, stmttype); - if (vacstmt->vacuum && all_rels) - { - /* - * It's a database-wide VACUUM. - * - * Compute the initially applicable OldestXmin and FreezeLimit XIDs, - * so that we can record these values at the end of the VACUUM. Note - * that individual tables may well be processed with newer values, but - * we can guarantee that no (non-shared) relations are processed with - * older ones. - * - * It is okay to record non-shared values in pg_database, even though - * we may vacuum shared relations with older cutoffs, because only the - * minimum of the values present in pg_database matters. We can be - * sure that shared relations have at some time been vacuumed with - * cutoffs no worse than the global minimum; for, if there is a - * backend in some other DB with xmin = OLDXMIN that's determining the - * cutoff with which we vacuum shared relations, it is not possible - * for that database to have a cutoff newer than OLDXMIN recorded in - * pg_database. - */ - vacuum_set_xid_limits(vacstmt, false, - &initialOldestXmin, - &initialFreezeLimit); - } - /* * Decide whether we need to start/commit our own transactions. * @@ -446,10 +418,8 @@ vacuum(VacuumStmt *vacstmt, List *relids) Oid relid = lfirst_oid(cur); if (vacstmt->vacuum) - { - if (!vacuum_rel(relid, vacstmt, RELKIND_RELATION)) - all_rels = false; /* forget about updating dbstats */ - } + vacuum_rel(relid, vacstmt, RELKIND_RELATION); + if (vacstmt->analyze) { MemoryContext old_context = NULL; @@ -525,6 +495,9 @@ vacuum(VacuumStmt *vacstmt, List *relids) if (vacstmt->vacuum) { + TransactionId minxid, + vacuumxid; + /* * If it was a database-wide VACUUM, print FSM usage statistics (we * don't make you be superuser to see these). @@ -532,17 +505,11 @@ vacuum(VacuumStmt *vacstmt, List *relids) if (all_rels) PrintFreeSpaceMapStatistics(elevel); - /* - * If we completed a database-wide VACUUM without skipping any - * relations, update the database's pg_database row with info about - * the transaction IDs used, and try to truncate pg_clog. - */ - if (all_rels) - { - vac_update_dbstats(MyDatabaseId, - initialOldestXmin, initialFreezeLimit); - vac_truncate_clog(initialOldestXmin, initialFreezeLimit); - } + /* Update pg_database.datminxid and datvacuumxid */ + vac_update_dbminxid(MyDatabaseId, &minxid, &vacuumxid); + + /* Try to truncate pg_clog. */ + vac_truncate_clog(minxid, vacuumxid); } /* @@ -688,7 +655,8 @@ vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel, */ void vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples, - bool hasindex) + bool hasindex, TransactionId minxid, + TransactionId vacuumxid) { Relation rd; HeapTuple ctup; @@ -736,6 +704,16 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples, dirty = true; } } + if (TransactionIdIsValid(minxid) && pgcform->relminxid != minxid) + { + pgcform->relminxid = minxid; + dirty = true; + } + if (TransactionIdIsValid(vacuumxid) && pgcform->relvacuumxid != vacuumxid) + { + pgcform->relvacuumxid = vacuumxid; + dirty = true; + } /* * If anything changed, write out the tuple @@ -748,10 +726,13 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples, /* - * vac_update_dbstats() -- update statistics for one database + * vac_update_dbminxid() -- update the minimum Xid present in one database * - * Update the whole-database statistics that are kept in its pg_database - * row, and the flat-file copy of pg_database. + * Update pg_database's datminxid and datvacuumxid, and the flat-file copy + * of it. datminxid is updated to the minimum of all relminxid found in + * pg_class. datvacuumxid is updated to the minimum of all relvacuumxid + * found in pg_class. The values are also returned in minxid and + * vacuumxid, respectively. * * We violate transaction semantics here by overwriting the database's * existing pg_database tuple with the new values. This is reasonably @@ -759,18 +740,67 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples, * commits. As with vac_update_relstats, this avoids leaving dead tuples * behind after a VACUUM. * - * This routine is shared by full and lazy VACUUM. Note that it is only - * applied after a database-wide VACUUM operation. + * This routine is shared by full and lazy VACUUM. */ static void -vac_update_dbstats(Oid dbid, - TransactionId vacuumXID, - TransactionId frozenXID) +vac_update_dbminxid(Oid dbid, TransactionId *minxid, TransactionId *vacuumxid) { - Relation relation; HeapTuple tuple; Form_pg_database dbform; + Relation relation; + SysScanDesc scan; + HeapTuple classTup; + TransactionId newMinXid = InvalidTransactionId; + TransactionId newVacXid = InvalidTransactionId; + bool dirty = false; + + /* + * We must seqscan pg_class to find the minimum Xid, because there + * is no index that can help us here. + */ + relation = heap_open(RelationRelationId, AccessShareLock); + + scan = systable_beginscan(relation, InvalidOid, false, + SnapshotNow, 0, NULL); + + while ((classTup = systable_getnext(scan)) != NULL) + { + Form_pg_class classForm; + + classForm = (Form_pg_class) GETSTRUCT(classTup); + + /* + * Only consider heap and TOAST tables (anything else should have + * InvalidTransactionId in both fields anyway.) + */ + if (classForm->relkind != RELKIND_RELATION && + classForm->relkind != RELKIND_TOASTVALUE) + continue; + + Assert(TransactionIdIsNormal(classForm->relminxid)); + Assert(TransactionIdIsNormal(classForm->relvacuumxid)); + /* + * Compute the minimum relminxid in all the tables in the database. + */ + if ((!TransactionIdIsValid(newMinXid) || + TransactionIdPrecedes(classForm->relminxid, newMinXid))) + newMinXid = classForm->relminxid; + + /* ditto, for relvacuumxid */ + if ((!TransactionIdIsValid(newVacXid) || + TransactionIdPrecedes(classForm->relvacuumxid, newVacXid))) + newVacXid = classForm->relvacuumxid; + } + + /* we're done with pg_class */ + systable_endscan(scan); + heap_close(relation, AccessShareLock); + + Assert(TransactionIdIsNormal(newMinXid)); + Assert(TransactionIdIsNormal(newVacXid)); + + /* Now fetch the pg_database tuple we need to update. */ relation = heap_open(DatabaseRelationId, RowExclusiveLock); /* Fetch a copy of the tuple to scribble on */ @@ -781,16 +811,29 @@ vac_update_dbstats(Oid dbid, elog(ERROR, "could not find tuple for database %u", dbid); dbform = (Form_pg_database) GETSTRUCT(tuple); - /* overwrite the existing statistics in the tuple */ - dbform->datvacuumxid = vacuumXID; - dbform->datfrozenxid = frozenXID; + if (TransactionIdPrecedes(dbform->datminxid, newMinXid)) + { + dbform->datminxid = newMinXid; + dirty = true; + } + if (TransactionIdPrecedes(dbform->datvacuumxid, newVacXid)) + { + dbform->datvacuumxid = newVacXid; + dirty = true; + } - heap_inplace_update(relation, tuple); + if (dirty) + heap_inplace_update(relation, tuple); + heap_freetuple(tuple); heap_close(relation, RowExclusiveLock); + /* set return values */ + *minxid = newMinXid; + *vacuumxid = newVacXid; + /* Mark the flat-file copy of pg_database for update at commit */ - database_file_update_needed(); + database_file_update_needed(); } @@ -814,18 +857,22 @@ vac_update_dbstats(Oid dbid, * applied after a database-wide VACUUM operation. */ static void -vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) +vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid) { TransactionId myXID = GetCurrentTransactionId(); + TransactionId minXID; + TransactionId vacuumXID; Relation relation; HeapScanDesc scan; HeapTuple tuple; int32 age; NameData oldest_datname; bool vacuumAlreadyWrapped = false; - bool frozenAlreadyWrapped = false; + bool minAlreadyWrapped = false; - /* init oldest_datname to sync with my frozenXID */ + /* Initialize the minimum values. */ + minXID = myminxid; + vacuumXID = myvacxid; namestrcpy(&oldest_datname, get_database_name(MyDatabaseId)); /* @@ -840,27 +887,20 @@ vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) { Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple); - /* Ignore non-connectable databases (eg, template0) */ - /* It's assumed that these have been frozen correctly */ - if (!dbform->datallowconn) - continue; + Assert(TransactionIdIsNormal(dbform->datvacuumxid)); + Assert(TransactionIdIsNormal(dbform->datminxid)); - if (TransactionIdIsNormal(dbform->datvacuumxid)) + if (TransactionIdPrecedes(myXID, dbform->datvacuumxid)) + vacuumAlreadyWrapped = true; + else if (TransactionIdPrecedes(dbform->datvacuumxid, vacuumXID)) + vacuumXID = dbform->datvacuumxid; + + if (TransactionIdPrecedes(myXID, dbform->datminxid)) + minAlreadyWrapped = true; + else if (TransactionIdPrecedes(dbform->datminxid, minXID)) { - if (TransactionIdPrecedes(myXID, dbform->datvacuumxid)) - vacuumAlreadyWrapped = true; - else if (TransactionIdPrecedes(dbform->datvacuumxid, vacuumXID)) - vacuumXID = dbform->datvacuumxid; - } - if (TransactionIdIsNormal(dbform->datfrozenxid)) - { - if (TransactionIdPrecedes(myXID, dbform->datfrozenxid)) - frozenAlreadyWrapped = true; - else if (TransactionIdPrecedes(dbform->datfrozenxid, frozenXID)) - { - frozenXID = dbform->datfrozenxid; - namecpy(&oldest_datname, &dbform->datname); - } + minXID = dbform->datminxid; + namecpy(&oldest_datname, &dbform->datname); } } @@ -887,7 +927,7 @@ vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) * Do not update varsup.c if we seem to have suffered wraparound already; * the computed XID might be bogus. */ - if (frozenAlreadyWrapped) + if (minAlreadyWrapped) { ereport(WARNING, (errmsg("some databases have not been vacuumed in over 1 billion transactions"), @@ -896,10 +936,10 @@ vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) } /* Update the wrap limit for GetNewTransactionId */ - SetTransactionIdLimit(frozenXID, &oldest_datname); + SetTransactionIdLimit(minXID, &oldest_datname); /* Give warning about impending wraparound problems */ - age = (int32) (myXID - frozenXID); + age = (int32) (myXID - minXID); if (age > (int32) ((MaxTransactionId >> 3) * 3)) ereport(WARNING, (errmsg("database \"%s\" must be vacuumed within %u transactions", @@ -907,6 +947,28 @@ vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) (MaxTransactionId >> 1) - age), errhint("To avoid a database shutdown, execute a full-database VACUUM in \"%s\".", NameStr(oldest_datname)))); + + /* + * Have the postmaster start an autovacuum iteration. If the user has + * autovacuum configured, this is not needed; otherwise, we need to make + * sure we have some mechanism to cope with transaction Id wraparound. + * Ideally this would only be needed for template databases, because all + * other databases should be kept nicely pruned by regular vacuuming. + * + * XXX -- the test we use here is fairly arbitrary. Note that in the + * autovacuum database-wide code, a template database is always processed + * with VACUUM FREEZE, so we can be sure that it will be truly frozen so + * it won't be need to be processed here again soon. + * + * FIXME -- here we could get into a kind of loop if the database being + * chosen is not actually a template database, because we'll not freeze + * it, so its age may not really decrease if there are any live + * non-freezable tuples. Consider forcing a vacuum freeze if autovacuum + * is invoked by a backend. On the other hand, forcing a vacuum freeze + * on a user database may not a be a very polite thing to do. + */ + if (!AutoVacuumingActive() && age > (int32) ((MaxTransactionId >> 3) * 3)) + SendPostmasterSignal(PMSIGNAL_START_AUTOVAC); } @@ -921,11 +983,6 @@ vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) /* * vacuum_rel() -- vacuum one heap relation * - * Returns TRUE if we actually processed the relation (or can ignore it - * for some reason), FALSE if we failed to process it due to permissions - * or other reasons. (A FALSE result really means that some data - * may have been left unvacuumed, so we can't update XID stats.) - * * Doing one heap at a time incurs extra overhead, since we need to * check that the heap exists again just before we vacuum it. The * reason that we do this is so that vacuuming can be spread across @@ -934,14 +991,13 @@ vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) * * At entry and exit, we are not inside a transaction. */ -static bool +static void vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind) { LOCKMODE lmode; Relation onerel; LockRelId onerelid; Oid toast_relid; - bool result; /* Begin a transaction for vacuuming this relation */ StartTransactionCommand(); @@ -970,7 +1026,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind) { StrategyHintVacuum(false); CommitTransactionCommand(); - return true; /* okay 'cause no data there */ + return; } /* @@ -1001,7 +1057,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind) relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); - return false; + return; } /* @@ -1016,7 +1072,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind) relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); - return false; + return; } /* @@ -1031,7 +1087,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind) relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); - return true; /* assume no long-lived data in temp tables */ + return; /* assume no long-lived data in temp tables */ } /* @@ -1060,8 +1116,6 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind) else lazy_vacuum_rel(onerel, vacstmt); - result = true; /* did the vacuum */ - /* all done with this class, but hold lock until commit */ relation_close(onerel, NoLock); @@ -1079,17 +1133,14 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind) * totally unimportant for toast relations. */ if (toast_relid != InvalidOid) - { - if (!vacuum_rel(toast_relid, vacstmt, RELKIND_TOASTVALUE)) - result = false; /* failed to vacuum the TOAST table? */ - } + vacuum_rel(toast_relid, vacstmt, RELKIND_TOASTVALUE); /* * Now release the session-level lock on the master table. */ UnlockRelationForSession(&onerelid, lmode); - return result; + return; } @@ -1121,6 +1172,8 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) int nindexes, i; VRelStats *vacrelstats; + TransactionId FreezeLimit, + OldestXmin; vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared, &OldestXmin, &FreezeLimit); @@ -1133,9 +1186,21 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) vacrelstats->rel_tuples = 0; vacrelstats->hasindex = false; + /* + * Set initial minimum Xid, which will be updated if a smaller Xid is found + * in the relation by scan_heap. + * + * We use RecentXmin here (the minimum Xid that belongs to a transaction + * that is still open according to our snapshot), because it is the + * earliest transaction that could insert new tuples in the table after our + * VACUUM is done. + */ + vacrelstats->minxid = RecentXmin; + /* scan the heap */ vacuum_pages.num_pages = fraged_pages.num_pages = 0; - scan_heap(vacrelstats, onerel, &vacuum_pages, &fraged_pages); + scan_heap(vacrelstats, onerel, &vacuum_pages, &fraged_pages, FreezeLimit, + OldestXmin); /* Now open all indexes of the relation */ vac_open_indexes(onerel, AccessExclusiveLock, &nindexes, &Irel); @@ -1163,7 +1228,7 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) { /* Try to shrink heap */ repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages, - nindexes, Irel); + nindexes, Irel, OldestXmin); vac_close_indexes(nindexes, Irel, NoLock); } else @@ -1181,7 +1246,8 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) /* update statistics in pg_class */ vac_update_relstats(RelationGetRelid(onerel), vacrelstats->rel_pages, - vacrelstats->rel_tuples, vacrelstats->hasindex); + vacrelstats->rel_tuples, vacrelstats->hasindex, + vacrelstats->minxid, OldestXmin); /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, @@ -1197,14 +1263,17 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) * deleted tuples), constructs fraged_pages (list of pages with free * space that tuples could be moved into), and calculates statistics * on the number of live tuples in the heap. + * + * It also updates the minimum Xid found anywhere on the table in + * vacrelstats->minxid, for later storing it in pg_class.relminxid. */ static void scan_heap(VRelStats *vacrelstats, Relation onerel, - VacPageList vacuum_pages, VacPageList fraged_pages) + VacPageList vacuum_pages, VacPageList fraged_pages, + TransactionId FreezeLimit, TransactionId OldestXmin) { BlockNumber nblocks, blkno; - HeapTupleData tuple; char *relname; VacPage vacpage; BlockNumber empty_pages, @@ -1318,6 +1387,7 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, { ItemId itemid = PageGetItemId(page, offnum); bool tupgone = false; + HeapTupleData tuple; /* * Collect un-used items too - it's possible to have indexes @@ -1453,12 +1523,23 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, } else { + TransactionId min; + num_tuples += 1; notup = false; if (tuple.t_len < min_tlen) min_tlen = tuple.t_len; if (tuple.t_len > max_tlen) max_tlen = tuple.t_len; + + /* + * If the tuple is alive, we consider it for the "minxid" + * calculations. + */ + min = vactuple_get_minxid(&tuple); + if (TransactionIdIsValid(min) && + TransactionIdPrecedes(min, vacrelstats->minxid)) + vacrelstats->minxid = min; } } /* scan along page */ @@ -1584,6 +1665,63 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, pg_rusage_show(&ru0)))); } +/* + * vactuple_get_minxid + * + * Get the minimum relevant Xid for a tuple, not considering FrozenXid. + * Return InvalidXid if none (i.e., xmin=FrozenXid, xmax=InvalidXid). + * This is for the purpose of calculating pg_class.relminxid for a table + * we're vacuuming. + */ +TransactionId +vactuple_get_minxid(HeapTuple tuple) +{ + TransactionId min = InvalidTransactionId; + + /* + * Initialize calculations with Xmin. NB -- may be FrozenXid and + * we don't want that one. + */ + if (TransactionIdIsNormal(HeapTupleHeaderGetXmin(tuple->t_data))) + min = HeapTupleHeaderGetXmin(tuple->t_data); + + /* + * If Xmax is not marked INVALID, we assume it's valid without making + * further checks on it --- it must be recently obsoleted or still running, + * else HeapTupleSatisfiesVacuum would have deemed it removable. + */ + if (!(tuple->t_data->t_infomask | HEAP_XMAX_INVALID)) + { + TransactionId xmax = HeapTupleHeaderGetXmax(tuple->t_data); + + /* If xmax is a plain Xid, consider it by itself */ + if (!(tuple->t_data->t_infomask | HEAP_XMAX_IS_MULTI)) + { + if (!TransactionIdIsValid(min) || + (TransactionIdIsNormal(xmax) && + TransactionIdPrecedes(xmax, min))) + min = xmax; + } + else + { + /* If it's a MultiXactId, consider each of its members */ + TransactionId *members; + int nmembers, + membno; + + nmembers = GetMultiXactIdMembers(xmax, &members); + + for (membno = 0; membno < nmembers; membno++) + { + if (!TransactionIdIsValid(min) || + TransactionIdPrecedes(members[membno], min)) + min = members[membno]; + } + } + } + + return min; +} /* * repair_frag() -- try to repair relation's fragmentation @@ -1598,7 +1736,7 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, static void repair_frag(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages, VacPageList fraged_pages, - int nindexes, Relation *Irel) + int nindexes, Relation *Irel, TransactionId OldestXmin) { TransactionId myXID = GetCurrentTransactionId(); Buffer dst_buffer = InvalidBuffer; @@ -2940,7 +3078,7 @@ scan_index(Relation indrel, double num_tuples) /* now update statistics in pg_class */ vac_update_relstats(RelationGetRelid(indrel), stats->num_pages, stats->num_index_tuples, - false); + false, InvalidTransactionId, InvalidTransactionId); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", @@ -3009,7 +3147,7 @@ vacuum_index(VacPageList vacpagelist, Relation indrel, /* now update statistics in pg_class */ vac_update_relstats(RelationGetRelid(indrel), stats->num_pages, stats->num_index_tuples, - false); + false, InvalidTransactionId, InvalidTransactionId); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 264cb437786..cc6a2a15a53 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -31,7 +31,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.72 2006/07/03 22:45:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.73 2006/07/10 16:20:50 alvherre Exp $ * *------------------------------------------------------------------------- */ @@ -42,6 +42,7 @@ #include "access/genam.h" #include "access/heapam.h" #include "access/xlog.h" +#include "catalog/catalog.h" #include "commands/vacuum.h" #include "miscadmin.h" #include "pgstat.h" @@ -72,6 +73,7 @@ typedef struct LVRelStats double tuples_deleted; BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */ Size threshold; /* minimum interesting free space */ + TransactionId minxid; /* minimum Xid present anywhere in table */ /* List of TIDs of tuples we intend to delete */ /* NB: this list is ordered by TID address */ int num_dead_tuples; /* current # of entries */ @@ -88,13 +90,11 @@ typedef struct LVRelStats static int elevel = -1; -static TransactionId OldestXmin; -static TransactionId FreezeLimit; - /* non-export function prototypes */ static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, - Relation *Irel, int nindexes); + Relation *Irel, int nindexes, TransactionId FreezeLimit, + TransactionId OldestXmin); static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats); static void lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, @@ -104,9 +104,10 @@ static void lazy_cleanup_index(Relation indrel, LVRelStats *vacrelstats); static int lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, int tupindex, LVRelStats *vacrelstats); -static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats); +static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats, + TransactionId OldestXmin); static BlockNumber count_nondeletable_pages(Relation onerel, - LVRelStats *vacrelstats); + LVRelStats *vacrelstats, TransactionId OldestXmin); static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks); static void lazy_record_dead_tuple(LVRelStats *vacrelstats, ItemPointer itemptr); @@ -122,7 +123,8 @@ static int vac_cmp_page_spaces(const void *left, const void *right); * lazy_vacuum_rel() -- perform LAZY VACUUM for one heap relation * * This routine vacuums a single heap, cleans out its indexes, and - * updates its num_pages and num_tuples statistics. + * updates its relpages and reltuples statistics, as well as the + * relminxid and relvacuumxid information. * * At entry, we have already established a transaction and opened * and locked the relation. @@ -135,6 +137,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) int nindexes; bool hasindex; BlockNumber possibly_freeable; + TransactionId OldestXmin, + FreezeLimit; if (vacstmt->verbose) elevel = INFO; @@ -150,12 +154,23 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) /* XXX should we scale it up or down? Adjust vacuum.c too, if so */ vacrelstats->threshold = GetAvgFSMRequestSize(&onerel->rd_node); + /* + * Set initial minimum Xid, which will be updated if a smaller Xid is found + * in the relation by lazy_scan_heap. + * + * We use RecentXmin here (the minimum Xid that belongs to a transaction + * that is still open according to our snapshot), because it is the + * earliest transaction that could concurrently insert new tuples in the + * table. + */ + vacrelstats->minxid = RecentXmin; + /* Open all indexes of the relation */ vac_open_indexes(onerel, ShareUpdateExclusiveLock, &nindexes, &Irel); hasindex = (nindexes > 0); /* Do the vacuuming */ - lazy_scan_heap(onerel, vacrelstats, Irel, nindexes); + lazy_scan_heap(onerel, vacrelstats, Irel, nindexes, FreezeLimit, OldestXmin); /* Done with indexes */ vac_close_indexes(nindexes, Irel, NoLock); @@ -169,7 +184,7 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages; if (possibly_freeable >= REL_TRUNCATE_MINIMUM || possibly_freeable >= vacrelstats->rel_pages / REL_TRUNCATE_FRACTION) - lazy_truncate_heap(onerel, vacrelstats); + lazy_truncate_heap(onerel, vacrelstats, OldestXmin); /* Update shared free space map with final free space info */ lazy_update_fsm(onerel, vacrelstats); @@ -178,7 +193,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) vac_update_relstats(RelationGetRelid(onerel), vacrelstats->rel_pages, vacrelstats->rel_tuples, - hasindex); + hasindex, + vacrelstats->minxid, OldestXmin); /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, @@ -193,10 +209,14 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) * and pages with free space, and calculates statistics on the number * of live tuples in the heap. When done, or when we run low on space * for dead-tuple TIDs, invoke vacuuming of indexes and heap. + * + * It also updates the minimum Xid found anywhere on the table in + * vacrelstats->minxid, for later storing it in pg_class.relminxid. */ static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, - Relation *Irel, int nindexes) + Relation *Irel, int nindexes, TransactionId FreezeLimit, + TransactionId OldestXmin) { BlockNumber nblocks, blkno; @@ -406,8 +426,19 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, } else { + TransactionId min; + num_tuples += 1; hastup = true; + + /* + * If the tuple is alive, we consider it for the "minxid" + * calculations. + */ + min = vactuple_get_minxid(&tuple); + if (TransactionIdIsValid(min) && + TransactionIdPrecedes(min, vacrelstats->minxid)) + vacrelstats->minxid = min; } } /* scan along page */ @@ -670,7 +701,7 @@ lazy_cleanup_index(Relation indrel, vac_update_relstats(RelationGetRelid(indrel), stats->num_pages, stats->num_index_tuples, - false); + false, InvalidTransactionId, InvalidTransactionId); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", @@ -691,7 +722,8 @@ lazy_cleanup_index(Relation indrel, * lazy_truncate_heap - try to truncate off any empty pages at the end */ static void -lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats) +lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats, + TransactionId OldestXmin) { BlockNumber old_rel_pages = vacrelstats->rel_pages; BlockNumber new_rel_pages; @@ -732,7 +764,7 @@ lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats) * because other backends could have added tuples to these pages whilst we * were vacuuming. */ - new_rel_pages = count_nondeletable_pages(onerel, vacrelstats); + new_rel_pages = count_nondeletable_pages(onerel, vacrelstats, OldestXmin); if (new_rel_pages >= old_rel_pages) { @@ -787,7 +819,8 @@ lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats) * Returns number of nondeletable pages (last nonempty page + 1). */ static BlockNumber -count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats) +count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats, + TransactionId OldestXmin) { BlockNumber blkno; HeapTupleData tuple; |