aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/vacuum.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands/vacuum.c')
-rw-r--r--src/backend/commands/vacuum.c490
1 files changed, 172 insertions, 318 deletions
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index e9f0bf363ea..b15fcc1059f 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.341 2006/10/04 00:29:51 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.342 2006/11/05 22:42:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -25,7 +25,6 @@
#include "access/clog.h"
#include "access/genam.h"
#include "access/heapam.h"
-#include "access/multixact.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/namespace.h"
@@ -36,7 +35,6 @@
#include "miscadmin.h"
#include "postmaster/autovacuum.h"
#include "storage/freespace.h"
-#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/acl.h"
@@ -53,6 +51,11 @@
/*
+ * GUC parameters
+ */
+int vacuum_freeze_min_age;
+
+/*
* VacPage structures keep track of each page on which we find useful
* amounts of free space.
*/
@@ -125,7 +128,6 @@ 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;
@@ -193,22 +195,21 @@ 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_dbminxid(Oid dbid,
- TransactionId *minxid,
- TransactionId *vacuumxid);
-static void vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid);
+static void vac_truncate_clog(TransactionId frozenXID);
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,
- TransactionId FreezeLimit, TransactionId OldestXmin);
+ VacPageList vacuum_pages, VacPageList fraged_pages);
static void repair_frag(VRelStats *vacrelstats, Relation onerel,
VacPageList vacuum_pages, VacPageList fraged_pages,
- int nindexes, Relation *Irel, TransactionId OldestXmin);
+ int nindexes, Relation *Irel);
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,
@@ -299,27 +300,6 @@ vacuum(VacuumStmt *vacstmt, List *relids)
in_outer_xact = IsInTransactionChain((void *) vacstmt);
/*
- * Disallow the combination VACUUM FULL FREEZE; although it would mostly
- * work, VACUUM FULL's ability to move tuples around means that it is
- * injecting its own XID into tuple visibility checks. We'd have to
- * guarantee that every moved tuple is properly marked XMIN_COMMITTED or
- * XMIN_INVALID before the end of the operation. There are corner cases
- * where this does not happen, and getting rid of them all seems hard (not
- * to mention fragile to maintain). On the whole it's not worth it
- * compared to telling people to use two operations. See pgsql-hackers
- * discussion of 27-Nov-2004, and comments below for update_hint_bits().
- *
- * Note: this is enforced here, and not in the grammar, since (a) we can
- * give a better error message, and (b) we might want to allow it again
- * someday.
- */
- if (vacstmt->vacuum && vacstmt->full && vacstmt->freeze)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("VACUUM FULL FREEZE is not supported"),
- errhint("Use VACUUM FULL, then VACUUM FREEZE.")));
-
- /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -492,23 +472,21 @@ vacuum(VacuumStmt *vacstmt, List *relids)
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
}
- if (vacstmt->vacuum)
+ if (vacstmt->vacuum && !IsAutoVacuumProcess())
{
- TransactionId minxid,
- vacuumxid;
+ /*
+ * Update pg_database.datfrozenxid, and truncate pg_clog if possible.
+ * (autovacuum.c does this for itself.)
+ */
+ vac_update_datfrozenxid();
/*
* If it was a database-wide VACUUM, print FSM usage statistics (we
- * don't make you be superuser to see these).
+ * don't make you be superuser to see these). We suppress this in
+ * autovacuum, too.
*/
if (all_rels)
PrintFreeSpaceMapStatistics(elevel);
-
- /* Update pg_database.datminxid and datvacuumxid */
- vac_update_dbminxid(MyDatabaseId, &minxid, &vacuumxid);
-
- /* Try to truncate pg_clog. */
- vac_truncate_clog(minxid, vacuumxid);
}
/*
@@ -591,12 +569,14 @@ vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel,
TransactionId *oldestXmin,
TransactionId *freezeLimit)
{
+ int freezemin;
TransactionId limit;
+ TransactionId safeLimit;
/*
* We can always ignore processes running lazy vacuum. This is because we
* use these values only for deciding which tuples we must keep in the
- * tables. Since lazy vacuum doesn't write its xid to the table, it's
+ * tables. Since lazy vacuum doesn't write its XID anywhere, it's
* safe to ignore it. In theory it could be problematic to ignore lazy
* vacuums on a full vacuum, but keep in mind that only one vacuum process
* can be working on a particular table at any time, and that each vacuum
@@ -606,30 +586,35 @@ vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel,
Assert(TransactionIdIsNormal(*oldestXmin));
- if (vacstmt->freeze)
- {
- /* FREEZE option: use oldest Xmin as freeze cutoff too */
- limit = *oldestXmin;
- }
- else
- {
- /*
- * Normal case: freeze cutoff is well in the past, to wit, about
- * halfway to the wrap horizon
- */
- limit = GetCurrentTransactionId() - (MaxTransactionId >> 2);
- }
+ /*
+ * Determine the minimum freeze age to use: as specified in the vacstmt,
+ * or vacuum_freeze_min_age, but in any case not more than half
+ * autovacuum_freeze_max_age, so that autovacuums to prevent XID
+ * wraparound won't occur too frequently.
+ */
+ freezemin = vacstmt->freeze_min_age;
+ if (freezemin < 0)
+ freezemin = vacuum_freeze_min_age;
+ freezemin = Min(freezemin, autovacuum_freeze_max_age / 2);
+ Assert(freezemin >= 0);
/*
- * Be careful not to generate a "permanent" XID
+ * Compute the cutoff XID, being careful not to generate a "permanent" XID
*/
+ limit = *oldestXmin - freezemin;
if (!TransactionIdIsNormal(limit))
limit = FirstNormalTransactionId;
/*
- * Ensure sane relationship of limits
+ * If oldestXmin is very far back (in practice, more than
+ * autovacuum_freeze_max_age / 2 XIDs old), complain and force a
+ * minimum freeze age of zero.
*/
- if (TransactionIdFollows(limit, *oldestXmin))
+ safeLimit = ReadNewTransactionId() - autovacuum_freeze_max_age;
+ if (!TransactionIdIsNormal(safeLimit))
+ safeLimit = FirstNormalTransactionId;
+
+ if (TransactionIdPrecedes(limit, safeLimit))
{
ereport(WARNING,
(errmsg("oldest xmin is far in the past"),
@@ -668,8 +653,7 @@ vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel,
*/
void
vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples,
- bool hasindex, TransactionId minxid,
- TransactionId vacuumxid)
+ bool hasindex, TransactionId frozenxid)
{
Relation rd;
HeapTuple ctup;
@@ -718,14 +702,15 @@ 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)
+
+ /*
+ * relfrozenxid should never go backward. Caller can pass
+ * InvalidTransactionId if it has no new data.
+ */
+ if (TransactionIdIsNormal(frozenxid) &&
+ TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid))
{
- pgcform->relvacuumxid = vacuumxid;
+ pgcform->relfrozenxid = frozenxid;
dirty = true;
}
@@ -740,35 +725,42 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples,
/*
- * vac_update_dbminxid() -- update the minimum Xid present in one database
+ * vac_update_datfrozenxid() -- update pg_database.datfrozenxid for our DB
*
- * 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.
+ * Update pg_database's datfrozenxid entry for our database to be the
+ * minimum of the pg_class.relfrozenxid values. If we are able to
+ * advance pg_database.datfrozenxid, also try to truncate pg_clog.
*
* We violate transaction semantics here by overwriting the database's
- * existing pg_database tuple with the new values. This is reasonably
- * safe since the new values are correct whether or not this transaction
+ * existing pg_database tuple with the new value. This is reasonably
+ * safe since the new value is correct whether or not this transaction
* commits. As with vac_update_relstats, this avoids leaving dead tuples
* behind after a VACUUM.
*
* This routine is shared by full and lazy VACUUM.
*/
-static void
-vac_update_dbminxid(Oid dbid, TransactionId *minxid, TransactionId *vacuumxid)
+void
+vac_update_datfrozenxid(void)
{
HeapTuple tuple;
Form_pg_database dbform;
Relation relation;
SysScanDesc scan;
HeapTuple classTup;
- TransactionId newMinXid = InvalidTransactionId;
- TransactionId newVacXid = InvalidTransactionId;
+ TransactionId newFrozenXid;
bool dirty = false;
/*
+ * Initialize the "min" calculation with RecentGlobalXmin. Any
+ * not-yet-committed pg_class entries for new tables must have
+ * relfrozenxid at least this high, because any other open xact must have
+ * RecentXmin >= its PGPROC.xmin >= our RecentGlobalXmin; see
+ * AddNewRelationTuple(). So we cannot produce a wrong minimum by
+ * starting with this.
+ */
+ newFrozenXid = RecentGlobalXmin;
+
+ /*
* We must seqscan pg_class to find the minimum Xid, because there is no
* index that can help us here.
*/
@@ -779,60 +771,46 @@ vac_update_dbminxid(Oid dbid, TransactionId *minxid, TransactionId *vacuumxid)
while ((classTup = systable_getnext(scan)) != NULL)
{
- Form_pg_class classForm;
-
- classForm = (Form_pg_class) GETSTRUCT(classTup);
+ Form_pg_class classForm = (Form_pg_class) GETSTRUCT(classTup);
/*
* Only consider heap and TOAST tables (anything else should have
- * InvalidTransactionId in both fields anyway.)
+ * InvalidTransactionId in relfrozenxid anyway.)
*/
if (classForm->relkind != RELKIND_RELATION &&
classForm->relkind != RELKIND_TOASTVALUE)
continue;
- Assert(TransactionIdIsNormal(classForm->relminxid));
- Assert(TransactionIdIsNormal(classForm->relvacuumxid));
+ Assert(TransactionIdIsNormal(classForm->relfrozenxid));
- /*
- * 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;
+ if (TransactionIdPrecedes(classForm->relfrozenxid, newFrozenXid))
+ newFrozenXid = classForm->relfrozenxid;
}
/* we're done with pg_class */
systable_endscan(scan);
heap_close(relation, AccessShareLock);
- Assert(TransactionIdIsNormal(newMinXid));
- Assert(TransactionIdIsNormal(newVacXid));
+ Assert(TransactionIdIsNormal(newFrozenXid));
/* Now fetch the pg_database tuple we need to update. */
relation = heap_open(DatabaseRelationId, RowExclusiveLock);
/* Fetch a copy of the tuple to scribble on */
tuple = SearchSysCacheCopy(DATABASEOID,
- ObjectIdGetDatum(dbid),
+ ObjectIdGetDatum(MyDatabaseId),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
- elog(ERROR, "could not find tuple for database %u", dbid);
+ elog(ERROR, "could not find tuple for database %u", MyDatabaseId);
dbform = (Form_pg_database) GETSTRUCT(tuple);
- if (TransactionIdPrecedes(dbform->datminxid, newMinXid))
- {
- dbform->datminxid = newMinXid;
- dirty = true;
- }
- if (TransactionIdPrecedes(dbform->datvacuumxid, newVacXid))
+ /*
+ * Don't allow datfrozenxid to go backward (probably can't happen anyway);
+ * and detect the common case where it doesn't go forward either.
+ */
+ if (TransactionIdPrecedes(dbform->datfrozenxid, newFrozenXid))
{
- dbform->datvacuumxid = newVacXid;
+ dbform->datfrozenxid = newFrozenXid;
dirty = true;
}
@@ -842,56 +820,57 @@ vac_update_dbminxid(Oid dbid, TransactionId *minxid, TransactionId *vacuumxid)
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();
+ /*
+ * If we were able to advance datfrozenxid, mark the flat-file copy of
+ * pg_database for update at commit, and see if we can truncate
+ * pg_clog.
+ */
+ if (dirty)
+ {
+ database_file_update_needed();
+ vac_truncate_clog(newFrozenXid);
+ }
}
/*
* vac_truncate_clog() -- attempt to truncate the commit log
*
- * Scan pg_database to determine the system-wide oldest datvacuumxid,
+ * Scan pg_database to determine the system-wide oldest datfrozenxid,
* and use it to truncate the transaction commit log (pg_clog).
- * Also update the XID wrap limit point maintained by varsup.c.
- *
- * We also generate a warning if the system-wide oldest datfrozenxid
- * seems to be in danger of wrapping around. This is a long-in-advance
- * warning; if we start getting uncomfortably close, GetNewTransactionId
- * will generate more-annoying warnings, and ultimately refuse to issue
- * any more new XIDs.
+ * Also update the XID wrap limit info maintained by varsup.c.
*
- * The passed XIDs are simply the ones I just wrote into my pg_database
- * entry. They're used to initialize the "min" calculations.
+ * The passed XID is simply the one I just wrote into my pg_database
+ * entry. It's used to initialize the "min" calculation.
*
- * 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. Note that it's
+ * only invoked when we've managed to change our DB's datfrozenxid
+ * entry.
*/
static void
-vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid)
+vac_truncate_clog(TransactionId frozenXID)
{
TransactionId myXID = GetCurrentTransactionId();
- TransactionId minXID;
- TransactionId vacuumXID;
Relation relation;
HeapScanDesc scan;
HeapTuple tuple;
- int32 age;
NameData oldest_datname;
- bool vacuumAlreadyWrapped = false;
- bool minAlreadyWrapped = false;
+ bool frozenAlreadyWrapped = false;
- /* Initialize the minimum values. */
- minXID = myminxid;
- vacuumXID = myvacxid;
+ /* init oldest_datname to sync with my frozenXID */
namestrcpy(&oldest_datname, get_database_name(MyDatabaseId));
/*
- * Note: the "already wrapped" cases should now be impossible due to the
- * defenses in GetNewTransactionId, but we keep them anyway.
+ * Scan pg_database to compute the minimum datfrozenxid
+ *
+ * Note: we need not worry about a race condition with new entries being
+ * inserted by CREATE DATABASE. Any such entry will have a copy of some
+ * existing DB's datfrozenxid, and that source DB cannot be ours because
+ * of the interlock against copying a DB containing an active backend.
+ * Hence the new entry will not reduce the minimum. Also, if two
+ * VACUUMs concurrently modify the datfrozenxid's of different databases,
+ * the worst possible outcome is that pg_clog is not truncated as
+ * aggressively as it could be.
*/
relation = heap_open(DatabaseRelationId, AccessShareLock);
@@ -901,19 +880,13 @@ vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid)
{
Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
- Assert(TransactionIdIsNormal(dbform->datvacuumxid));
- Assert(TransactionIdIsNormal(dbform->datminxid));
-
- if (TransactionIdPrecedes(myXID, dbform->datvacuumxid))
- vacuumAlreadyWrapped = true;
- else if (TransactionIdPrecedes(dbform->datvacuumxid, vacuumXID))
- vacuumXID = dbform->datvacuumxid;
+ Assert(TransactionIdIsNormal(dbform->datfrozenxid));
- if (TransactionIdPrecedes(myXID, dbform->datminxid))
- minAlreadyWrapped = true;
- else if (TransactionIdPrecedes(dbform->datminxid, minXID))
+ if (TransactionIdPrecedes(myXID, dbform->datfrozenxid))
+ frozenAlreadyWrapped = true;
+ else if (TransactionIdPrecedes(dbform->datfrozenxid, frozenXID))
{
- minXID = dbform->datminxid;
+ frozenXID = dbform->datfrozenxid;
namecpy(&oldest_datname, &dbform->datname);
}
}
@@ -924,9 +897,11 @@ vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid)
/*
* Do not truncate CLOG if we seem to have suffered wraparound already;
- * the computed minimum XID might be bogus.
+ * the computed minimum XID might be bogus. This case should now be
+ * impossible due to the defenses in GetNewTransactionId, but we keep the
+ * test anyway.
*/
- if (vacuumAlreadyWrapped)
+ if (frozenAlreadyWrapped)
{
ereport(WARNING,
(errmsg("some databases have not been vacuumed in over 2 billion transactions"),
@@ -934,55 +909,14 @@ vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid)
return;
}
- /* Truncate CLOG to the oldest vacuumxid */
- TruncateCLOG(vacuumXID);
+ /* Truncate CLOG to the oldest frozenxid */
+ TruncateCLOG(frozenXID);
/*
- * Do not update varsup.c if we seem to have suffered wraparound already;
- * the computed XID might be bogus.
+ * Update the wrap limit for GetNewTransactionId. Note: this function
+ * will also signal the postmaster for an(other) autovac cycle if needed.
*/
- if (minAlreadyWrapped)
- {
- ereport(WARNING,
- (errmsg("some databases have not been vacuumed in over 1 billion transactions"),
- errhint("Better vacuum them soon, or you may have a wraparound failure.")));
- return;
- }
-
- /* Update the wrap limit for GetNewTransactionId */
- SetTransactionIdLimit(minXID, &oldest_datname);
-
- /* Give warning about impending wraparound problems */
- age = (int32) (myXID - minXID);
- if (age > (int32) ((MaxTransactionId >> 3) * 3))
- ereport(WARNING,
- (errmsg("database \"%s\" must be vacuumed within %u transactions",
- NameStr(oldest_datname),
- (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);
+ SetTransactionIdLimit(frozenXID, &oldest_datname);
}
@@ -1118,7 +1052,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind)
{
relation_close(onerel, lmode);
CommitTransactionCommand();
- return; /* assume no long-lived data in temp tables */
+ return;
}
/*
@@ -1208,8 +1142,6 @@ 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);
@@ -1222,21 +1154,9 @@ 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, FreezeLimit,
- OldestXmin);
+ scan_heap(vacrelstats, onerel, &vacuum_pages, &fraged_pages);
/* Now open all indexes of the relation */
vac_open_indexes(onerel, AccessExclusiveLock, &nindexes, &Irel);
@@ -1264,7 +1184,7 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
{
/* Try to shrink heap */
repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages,
- nindexes, Irel, OldestXmin);
+ nindexes, Irel);
vac_close_indexes(nindexes, Irel, NoLock);
}
else
@@ -1283,7 +1203,7 @@ 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->minxid, OldestXmin);
+ FreezeLimit);
/* report results to the stats collector, too */
pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared,
@@ -1299,14 +1219,10 @@ 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,
- TransactionId FreezeLimit, TransactionId OldestXmin)
+ VacPageList vacuum_pages, VacPageList fraged_pages)
{
BlockNumber nblocks,
blkno;
@@ -1357,8 +1273,9 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
Buffer buf;
OffsetNumber offnum,
maxoff;
- bool pgchanged,
- notup;
+ bool notup;
+ OffsetNumber frozen[MaxOffsetNumber];
+ int nfrozen;
vacuum_delay_point();
@@ -1414,7 +1331,7 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
continue;
}
- pgchanged = false;
+ nfrozen = 0;
notup = true;
maxoff = PageGetMaxOffsetNumber(page);
for (offnum = FirstOffsetNumber;
@@ -1446,24 +1363,7 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
tupgone = true; /* we can delete the tuple */
break;
case HEAPTUPLE_LIVE:
-
- /*
- * Tuple is good. Consider whether to replace its xmin
- * value with FrozenTransactionId.
- */
- if (TransactionIdIsNormal(HeapTupleHeaderGetXmin(tuple.t_data)) &&
- TransactionIdPrecedes(HeapTupleHeaderGetXmin(tuple.t_data),
- FreezeLimit))
- {
- HeapTupleHeaderSetXmin(tuple.t_data, FrozenTransactionId);
- /* infomask should be okay already */
- Assert(tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED);
- pgchanged = true;
- }
-
- /*
- * Other checks...
- */
+ /* Tuple is good --- but let's do some validity checks */
if (onerel->rd_rel->relhasoids &&
!OidIsValid(HeapTupleGetOid(&tuple)))
elog(WARNING, "relation \"%s\" TID %u/%u: OID is invalid",
@@ -1559,8 +1459,6 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
}
else
{
- TransactionId min;
-
num_tuples += 1;
notup = false;
if (tuple.t_len < min_tlen)
@@ -1569,13 +1467,12 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
max_tlen = tuple.t_len;
/*
- * If the tuple is alive, we consider it for the "minxid"
- * calculations.
+ * Each non-removable tuple must be checked to see if it
+ * needs freezing.
*/
- min = vactuple_get_minxid(&tuple);
- if (TransactionIdIsValid(min) &&
- TransactionIdPrecedes(min, vacrelstats->minxid))
- vacrelstats->minxid = min;
+ if (heap_freeze_tuple(tuple.t_data, FreezeLimit,
+ InvalidBuffer))
+ frozen[nfrozen++] = offnum;
}
} /* scan along page */
@@ -1627,8 +1524,26 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
else
empty_end_pages = 0;
- if (pgchanged)
+ /*
+ * If we froze any tuples, mark the buffer dirty, and write a WAL
+ * record recording the changes. We must log the changes to be
+ * crash-safe against future truncation of CLOG.
+ */
+ if (nfrozen > 0)
+ {
MarkBufferDirty(buf);
+ /* no XLOG for temp tables, though */
+ if (!onerel->rd_istemp)
+ {
+ XLogRecPtr recptr;
+
+ recptr = log_heap_freeze(onerel, buf, FreezeLimit,
+ frozen, nfrozen);
+ PageSetLSN(page, recptr);
+ PageSetTLI(page, ThisTimeLineID);
+ }
+ }
+
UnlockReleaseBuffer(buf);
}
@@ -1701,63 +1616,6 @@ 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
@@ -1772,7 +1630,7 @@ vactuple_get_minxid(HeapTuple tuple)
static void
repair_frag(VRelStats *vacrelstats, Relation onerel,
VacPageList vacuum_pages, VacPageList fraged_pages,
- int nindexes, Relation *Irel, TransactionId OldestXmin)
+ int nindexes, Relation *Irel)
{
TransactionId myXID = GetCurrentTransactionId();
Buffer dst_buffer = InvalidBuffer;
@@ -2903,8 +2761,8 @@ move_plain_tuple(Relation rel,
* update_hint_bits() -- update hint bits in destination pages
*
* Scan all the pages that we moved tuples onto and update tuple status bits.
- * This is normally not really necessary, but it will save time for future
- * transactions examining these tuples.
+ * This is not really necessary, but it will save time for future transactions
+ * examining these tuples.
*
* This pass guarantees that all HEAP_MOVED_IN tuples are marked as
* XMIN_COMMITTED, so that future tqual tests won't need to check their XVAC.
@@ -2919,13 +2777,9 @@ move_plain_tuple(Relation rel,
* To completely ensure that no MOVED_OFF tuples remain unmarked, we'd have
* to remember and revisit those pages too.
*
- * Because of this omission, VACUUM FULL FREEZE is not a safe combination;
- * it's possible that the VACUUM's own XID remains exposed as something that
- * tqual tests would need to check.
- *
- * For the non-freeze case, one wonders whether it wouldn't be better to skip
- * this work entirely, and let the tuple status updates happen someplace
- * that's not holding an exclusive lock on the relation.
+ * One wonders whether it wouldn't be better to skip this work entirely,
+ * and let the tuple status updates happen someplace that's not holding an
+ * exclusive lock on the relation.
*/
static void
update_hint_bits(Relation rel, VacPageList fraged_pages, int num_fraged_pages,
@@ -3114,7 +2968,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, InvalidTransactionId, InvalidTransactionId);
+ false, InvalidTransactionId);
ereport(elevel,
(errmsg("index \"%s\" now contains %.0f row versions in %u pages",
@@ -3183,7 +3037,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, InvalidTransactionId, InvalidTransactionId);
+ false, InvalidTransactionId);
ereport(elevel,
(errmsg("index \"%s\" now contains %.0f row versions in %u pages",