mirror of
https://github.com/postgres/postgres.git
synced 2025-10-21 02:52:47 +03:00
Transaction IDs wrap around, per my proposal of 13-Aug-01. More
documentation to come, but the code is all here. initdb forced.
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.207 2001/08/10 18:57:35 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.208 2001/08/26 16:55:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -21,11 +21,13 @@
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "access/clog.h"
|
||||
#include "access/genam.h"
|
||||
#include "access/heapam.h"
|
||||
#include "access/xlog.h"
|
||||
#include "catalog/catalog.h"
|
||||
#include "catalog/catname.h"
|
||||
#include "catalog/pg_database.h"
|
||||
#include "catalog/pg_index.h"
|
||||
#include "commands/vacuum.h"
|
||||
#include "executor/executor.h"
|
||||
@@ -108,15 +110,24 @@ static MemoryContext vac_context = NULL;
|
||||
|
||||
static int MESSAGE_LEVEL; /* message level */
|
||||
|
||||
static TransactionId XmaxRecent;
|
||||
static TransactionId OldestXmin;
|
||||
static TransactionId FreezeLimit;
|
||||
|
||||
static TransactionId initialOldestXmin;
|
||||
static TransactionId initialFreezeLimit;
|
||||
|
||||
|
||||
/* non-export function prototypes */
|
||||
static void vacuum_init(void);
|
||||
static void vacuum_shutdown(void);
|
||||
static void vacuum_init(VacuumStmt *vacstmt);
|
||||
static void vacuum_shutdown(VacuumStmt *vacstmt);
|
||||
static VRelList getrels(Name VacRelP, const char *stmttype);
|
||||
static void vac_update_dbstats(Oid dbid,
|
||||
TransactionId vacuumXID,
|
||||
TransactionId frozenXID);
|
||||
static void vac_truncate_clog(TransactionId vacuumXID,
|
||||
TransactionId frozenXID);
|
||||
static void vacuum_rel(Oid relid, VacuumStmt *vacstmt);
|
||||
static void full_vacuum_rel(Relation onerel);
|
||||
static void full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt);
|
||||
static void scan_heap(VRelStats *vacrelstats, Relation onerel,
|
||||
VacPageList vacuum_pages, VacPageList fraged_pages);
|
||||
static void repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
@@ -213,7 +224,7 @@ vacuum(VacuumStmt *vacstmt)
|
||||
/*
|
||||
* Start up the vacuum cleaner.
|
||||
*/
|
||||
vacuum_init();
|
||||
vacuum_init(vacstmt);
|
||||
|
||||
/*
|
||||
* Process each selected relation. We are careful to process
|
||||
@@ -230,21 +241,8 @@ vacuum(VacuumStmt *vacstmt)
|
||||
analyze_rel(cur->vrl_relid, vacstmt);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we did a complete vacuum, then flush the init file that relcache.c
|
||||
* uses to save startup time. The next backend startup will rebuild the
|
||||
* init file with up-to-date information from pg_class. This lets the
|
||||
* optimizer see the stats that we've collected for certain critical
|
||||
* system indexes. See relcache.c for more details.
|
||||
*
|
||||
* Ignore any failure to unlink the file, since it might not be there if
|
||||
* no backend has been started since the last vacuum.
|
||||
*/
|
||||
if (vacstmt->vacrel == NULL)
|
||||
unlink(RELCACHE_INIT_FILENAME);
|
||||
|
||||
/* clean up */
|
||||
vacuum_shutdown();
|
||||
vacuum_shutdown(vacstmt);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -268,20 +266,68 @@ vacuum(VacuumStmt *vacstmt)
|
||||
* PostgresMain().
|
||||
*/
|
||||
static void
|
||||
vacuum_init(void)
|
||||
vacuum_init(VacuumStmt *vacstmt)
|
||||
{
|
||||
if (vacstmt->vacuum && vacstmt->vacrel == NULL)
|
||||
{
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/* matches the StartTransaction in PostgresMain() */
|
||||
CommitTransactionCommand();
|
||||
}
|
||||
|
||||
static void
|
||||
vacuum_shutdown(void)
|
||||
vacuum_shutdown(VacuumStmt *vacstmt)
|
||||
{
|
||||
/* on entry, we are not in a transaction */
|
||||
|
||||
/* matches the CommitTransaction in PostgresMain() */
|
||||
StartTransactionCommand();
|
||||
|
||||
/*
|
||||
* If we did a database-wide VACUUM, update the database's pg_database
|
||||
* row with info about the transaction IDs used, and try to truncate
|
||||
* pg_clog.
|
||||
*/
|
||||
if (vacstmt->vacuum && vacstmt->vacrel == NULL)
|
||||
{
|
||||
vac_update_dbstats(MyDatabaseId,
|
||||
initialOldestXmin, initialFreezeLimit);
|
||||
vac_truncate_clog(initialOldestXmin, initialFreezeLimit);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we did a complete vacuum or analyze, then flush the init file that
|
||||
* relcache.c uses to save startup time. The next backend startup will
|
||||
* rebuild the init file with up-to-date information from pg_class.
|
||||
* This lets the optimizer see the stats that we've collected for certain
|
||||
* critical system indexes. See relcache.c for more details.
|
||||
*
|
||||
* Ignore any failure to unlink the file, since it might not be there if
|
||||
* no backend has been started since the last vacuum.
|
||||
*/
|
||||
if (vacstmt->vacrel == NULL)
|
||||
unlink(RELCACHE_INIT_FILENAME);
|
||||
|
||||
/*
|
||||
* Clean up working storage --- note we must do this after
|
||||
* StartTransactionCommand, else we might be trying to delete the
|
||||
@@ -382,6 +428,52 @@ getrels(Name VacRelP, const char *stmttype)
|
||||
return vrl;
|
||||
}
|
||||
|
||||
/*
|
||||
* vacuum_set_xid_limits() -- compute oldest-Xmin and freeze cutoff points
|
||||
*/
|
||||
void
|
||||
vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel,
|
||||
TransactionId *oldestXmin,
|
||||
TransactionId *freezeLimit)
|
||||
{
|
||||
TransactionId limit;
|
||||
|
||||
*oldestXmin = GetOldestXmin(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);
|
||||
}
|
||||
|
||||
/*
|
||||
* Be careful not to generate a "permanent" XID
|
||||
*/
|
||||
if (!TransactionIdIsNormal(limit))
|
||||
limit = FirstNormalTransactionId;
|
||||
|
||||
/*
|
||||
* Ensure sane relationship of limits
|
||||
*/
|
||||
if (TransactionIdFollows(limit, *oldestXmin))
|
||||
{
|
||||
elog(NOTICE, "oldest Xmin is far in the past --- close open transactions soon to avoid wraparound problems");
|
||||
limit = *oldestXmin;
|
||||
}
|
||||
|
||||
*freezeLimit = limit;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* vac_update_relstats() -- update statistics for one relation
|
||||
@@ -449,6 +541,122 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* vac_update_dbstats() -- update statistics for one database
|
||||
*
|
||||
* Update the whole-database statistics that are kept in its pg_database
|
||||
* row.
|
||||
*
|
||||
* We violate no-overwrite semantics here by storing new values for the
|
||||
* statistics columns directly into the tuple that's already on the page.
|
||||
* As with vac_update_relstats, this avoids leaving dead tuples behind
|
||||
* after a VACUUM; which is good since GetRawDatabaseInfo
|
||||
* can get confused by finding dead tuples in pg_database.
|
||||
*
|
||||
* This routine is shared by full and lazy VACUUM. Note that it is only
|
||||
* applied after a database-wide VACUUM operation.
|
||||
*/
|
||||
static void
|
||||
vac_update_dbstats(Oid dbid,
|
||||
TransactionId vacuumXID,
|
||||
TransactionId frozenXID)
|
||||
{
|
||||
Relation relation;
|
||||
ScanKeyData entry[1];
|
||||
HeapScanDesc scan;
|
||||
HeapTuple tuple;
|
||||
Form_pg_database dbform;
|
||||
|
||||
relation = heap_openr(DatabaseRelationName, RowExclusiveLock);
|
||||
|
||||
/* Must use a heap scan, since there's no syscache for pg_database */
|
||||
ScanKeyEntryInitialize(&entry[0], 0x0,
|
||||
ObjectIdAttributeNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(dbid));
|
||||
|
||||
scan = heap_beginscan(relation, 0, SnapshotNow, 1, entry);
|
||||
|
||||
tuple = heap_getnext(scan, 0);
|
||||
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
elog(ERROR, "database %u does not exist", dbid);
|
||||
|
||||
dbform = (Form_pg_database) GETSTRUCT(tuple);
|
||||
|
||||
/* overwrite the existing statistics in the tuple */
|
||||
dbform->datvacuumxid = vacuumXID;
|
||||
dbform->datfrozenxid = frozenXID;
|
||||
|
||||
/* invalidate the tuple in the cache and write the buffer */
|
||||
RelationInvalidateHeapTuple(relation, tuple);
|
||||
WriteNoReleaseBuffer(scan->rs_cbuf);
|
||||
|
||||
heap_endscan(scan);
|
||||
|
||||
heap_close(relation, RowExclusiveLock);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* vac_truncate_clog() -- attempt to truncate the commit log
|
||||
*
|
||||
* Scan pg_database to determine the system-wide oldest datvacuumxid,
|
||||
* and use it to truncate the transaction commit log (pg_clog).
|
||||
* Also generate a warning if the system-wide oldest datfrozenxid
|
||||
* seems to be in danger of wrapping around.
|
||||
*
|
||||
* The passed XIDs are simply the ones I just wrote into my pg_database
|
||||
* entry. They're used to initialize the "min" calculations.
|
||||
*
|
||||
* This routine is shared by full and lazy VACUUM. Note that it is only
|
||||
* applied after a database-wide VACUUM operation.
|
||||
*/
|
||||
static void
|
||||
vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID)
|
||||
{
|
||||
Relation relation;
|
||||
HeapScanDesc scan;
|
||||
HeapTuple tuple;
|
||||
int32 age;
|
||||
|
||||
relation = heap_openr(DatabaseRelationName, AccessShareLock);
|
||||
|
||||
scan = heap_beginscan(relation, 0, SnapshotNow, 0, NULL);
|
||||
|
||||
while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
|
||||
{
|
||||
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;
|
||||
|
||||
if (TransactionIdIsNormal(dbform->datvacuumxid) &&
|
||||
TransactionIdPrecedes(dbform->datvacuumxid, vacuumXID))
|
||||
vacuumXID = dbform->datvacuumxid;
|
||||
if (TransactionIdIsNormal(dbform->datfrozenxid) &&
|
||||
TransactionIdPrecedes(dbform->datfrozenxid, frozenXID))
|
||||
frozenXID = dbform->datfrozenxid;
|
||||
}
|
||||
|
||||
heap_endscan(scan);
|
||||
|
||||
heap_close(relation, AccessShareLock);
|
||||
|
||||
/* Truncate CLOG to the oldest vacuumxid */
|
||||
TruncateCLOG(vacuumXID);
|
||||
|
||||
/* Give warning about impending wraparound problems */
|
||||
age = (int32) (GetCurrentTransactionId() - frozenXID);
|
||||
if (age > (int32) ((MaxTransactionId >> 3) * 3))
|
||||
elog(NOTICE, "Some databases have not been vacuumed in %d transactions."
|
||||
"\n\tBetter vacuum them within %d transactions,"
|
||||
"\n\tor you may have a wraparound failure.",
|
||||
age, (int32) (MaxTransactionId >> 1) - age);
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Code common to both flavors of VACUUM *
|
||||
@@ -550,7 +758,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt)
|
||||
* Do the actual work --- either FULL or "lazy" vacuum
|
||||
*/
|
||||
if (vacstmt->full)
|
||||
full_vacuum_rel(onerel);
|
||||
full_vacuum_rel(onerel, vacstmt);
|
||||
else
|
||||
lazy_vacuum_rel(onerel, vacstmt);
|
||||
|
||||
@@ -597,7 +805,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt)
|
||||
* and locked the relation.
|
||||
*/
|
||||
static void
|
||||
full_vacuum_rel(Relation onerel)
|
||||
full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
|
||||
{
|
||||
VacPageListData vacuum_pages; /* List of pages to vacuum and/or
|
||||
* clean indexes */
|
||||
@@ -613,7 +821,8 @@ full_vacuum_rel(Relation onerel)
|
||||
IsSystemRelationName(RelationGetRelationName(onerel)))
|
||||
reindex = true;
|
||||
|
||||
GetXmaxRecent(&XmaxRecent);
|
||||
vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared,
|
||||
&OldestXmin, &FreezeLimit);
|
||||
|
||||
/*
|
||||
* Set up statistics-gathering machinery.
|
||||
@@ -845,12 +1054,25 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
|
||||
tupgone = false;
|
||||
sv_infomask = tuple.t_data->t_infomask;
|
||||
|
||||
switch (HeapTupleSatisfiesVacuum(tuple.t_data, XmaxRecent))
|
||||
switch (HeapTupleSatisfiesVacuum(tuple.t_data, OldestXmin))
|
||||
{
|
||||
case HEAPTUPLE_DEAD:
|
||||
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(tuple.t_data->t_xmin) &&
|
||||
TransactionIdPrecedes(tuple.t_data->t_xmin,
|
||||
FreezeLimit))
|
||||
{
|
||||
tuple.t_data->t_xmin = FrozenTransactionId;
|
||||
tuple.t_data->t_infomask &= ~HEAP_XMIN_INVALID;
|
||||
tuple.t_data->t_infomask |= HEAP_XMIN_COMMITTED;
|
||||
pgchanged = true;
|
||||
}
|
||||
break;
|
||||
case HEAPTUPLE_RECENTLY_DEAD:
|
||||
/*
|
||||
@@ -1312,7 +1534,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
* tuples to another places.
|
||||
*/
|
||||
if ((tuple.t_data->t_infomask & HEAP_UPDATED &&
|
||||
!TransactionIdPrecedes(tuple.t_data->t_xmin, XmaxRecent)) ||
|
||||
!TransactionIdPrecedes(tuple.t_data->t_xmin, OldestXmin)) ||
|
||||
(!(tuple.t_data->t_infomask & HEAP_XMAX_INVALID) &&
|
||||
!(ItemPointerEquals(&(tuple.t_self),
|
||||
&(tuple.t_data->t_ctid)))))
|
||||
@@ -1362,7 +1584,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
|
||||
/*
|
||||
* This means that in the middle of chain there
|
||||
* was tuple updated by older (than XmaxRecent)
|
||||
* was tuple updated by older (than OldestXmin)
|
||||
* xaction and this tuple is already deleted by
|
||||
* me. Actually, upper part of chain should be
|
||||
* removed and seems that this should be handled
|
||||
@@ -1430,7 +1652,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
|
||||
|
||||
/* All done ? */
|
||||
if (!(tp.t_data->t_infomask & HEAP_UPDATED) ||
|
||||
TransactionIdPrecedes(tp.t_data->t_xmin, XmaxRecent))
|
||||
TransactionIdPrecedes(tp.t_data->t_xmin, OldestXmin))
|
||||
break;
|
||||
|
||||
/* Well, try to find tuple with old row version */
|
||||
|
Reference in New Issue
Block a user