mirror of
https://github.com/postgres/postgres.git
synced 2025-07-05 07:21:24 +03:00
Fix recently-understood problems with handling of XID freezing, particularly
in PITR scenarios. We now WAL-log the replacement of old XIDs with FrozenTransactionId, so that such replacement is guaranteed to propagate to PITR slave databases. Also, rather than relying on hint-bit updates to be preserved, pg_clog is not truncated until all instances of an XID are known to have been replaced by FrozenTransactionId. Add new GUC variables and pg_autovacuum columns to allow management of the freezing policy, so that users can trade off the size of pg_clog against the amount of freezing work done. Revise the already-existing code that forces autovacuum of tables approaching the wraparound point to make it more bulletproof; also, revise the autovacuum logic so that anti-wraparound vacuuming is done per-table rather than per-database. initdb forced because of changes in pg_class, pg_database, and pg_autovacuum catalogs. Heikki Linnakangas, Simon Riggs, and Tom Lane.
This commit is contained in:
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.100 2006/10/05 17:57:40 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.101 2006/11/05 22:42:08 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -421,7 +421,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt)
|
||||
vac_update_relstats(RelationGetRelid(onerel),
|
||||
RelationGetNumberOfBlocks(onerel),
|
||||
totalrows, hasindex,
|
||||
InvalidTransactionId, InvalidTransactionId);
|
||||
InvalidTransactionId);
|
||||
|
||||
for (ind = 0; ind < nindexes; ind++)
|
||||
{
|
||||
@ -432,7 +432,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt)
|
||||
vac_update_relstats(RelationGetRelid(Irel[ind]),
|
||||
RelationGetNumberOfBlocks(Irel[ind]),
|
||||
totalindexrows, false,
|
||||
InvalidTransactionId, InvalidTransactionId);
|
||||
InvalidTransactionId);
|
||||
}
|
||||
|
||||
/* report results to the stats collector, too */
|
||||
|
@ -13,7 +13,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.186 2006/10/18 22:44:12 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.187 2006/11/05 22:42:08 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -53,8 +53,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 *dbMinXidP,
|
||||
Oid *dbLastSysOidP, TransactionId *dbFrozenXidP,
|
||||
Oid *dbTablespace);
|
||||
static bool have_createdb_privilege(void);
|
||||
static void remove_dbtablespaces(Oid db_id);
|
||||
@ -75,8 +74,7 @@ createdb(const CreatedbStmt *stmt)
|
||||
bool src_istemplate;
|
||||
bool src_allowconn;
|
||||
Oid src_lastsysoid;
|
||||
TransactionId src_vacuumxid;
|
||||
TransactionId src_minxid;
|
||||
TransactionId src_frozenxid;
|
||||
Oid src_deftablespace;
|
||||
volatile Oid dst_deftablespace;
|
||||
Relation pg_database_rel;
|
||||
@ -228,7 +226,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_minxid, &src_deftablespace))
|
||||
&src_frozenxid, &src_deftablespace))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_DATABASE),
|
||||
errmsg("template database \"%s\" does not exist",
|
||||
@ -366,8 +364,7 @@ createdb(const CreatedbStmt *stmt)
|
||||
new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(true);
|
||||
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_datminxid - 1] = TransactionIdGetDatum(src_minxid);
|
||||
new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid);
|
||||
new_record[Anum_pg_database_dattablespace - 1] = ObjectIdGetDatum(dst_deftablespace);
|
||||
|
||||
/*
|
||||
@ -565,7 +562,7 @@ dropdb(const char *dbname, bool missing_ok)
|
||||
pgdbrel = heap_open(DatabaseRelationId, RowExclusiveLock);
|
||||
|
||||
if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
|
||||
&db_istemplate, NULL, NULL, NULL, NULL, NULL))
|
||||
&db_istemplate, NULL, NULL, NULL, NULL))
|
||||
{
|
||||
if (!missing_ok)
|
||||
{
|
||||
@ -689,7 +686,7 @@ RenameDatabase(const char *oldname, const char *newname)
|
||||
rel = heap_open(DatabaseRelationId, RowExclusiveLock);
|
||||
|
||||
if (!get_db_info(oldname, AccessExclusiveLock, &db_id, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL))
|
||||
NULL, NULL, NULL, NULL, NULL))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_DATABASE),
|
||||
errmsg("database \"%s\" does not exist", oldname)));
|
||||
@ -1067,8 +1064,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 *dbMinXidP,
|
||||
Oid *dbLastSysOidP, TransactionId *dbFrozenXidP,
|
||||
Oid *dbTablespace)
|
||||
{
|
||||
bool result = false;
|
||||
@ -1154,12 +1150,9 @@ get_db_info(const char *name, LOCKMODE lockmode,
|
||||
/* last system OID used in database */
|
||||
if (dbLastSysOidP)
|
||||
*dbLastSysOidP = dbform->datlastsysoid;
|
||||
/* limit of vacuumed XIDs */
|
||||
if (dbVacuumXidP)
|
||||
*dbVacuumXidP = dbform->datvacuumxid;
|
||||
/* limit of min XIDs */
|
||||
if (dbMinXidP)
|
||||
*dbMinXidP = dbform->datminxid;
|
||||
/* limit of frozen XIDs */
|
||||
if (dbFrozenXidP)
|
||||
*dbFrozenXidP = dbform->datfrozenxid;
|
||||
/* default tablespace for this database */
|
||||
if (dbTablespace)
|
||||
*dbTablespace = dbform->dattablespace;
|
||||
|
@ -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"
|
||||
@ -52,6 +50,11 @@
|
||||
#include "pgstat.h"
|
||||
|
||||
|
||||
/*
|
||||
* 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,
|
||||
@ -298,27 +299,6 @@ vacuum(VacuumStmt *vacstmt, List *relids)
|
||||
else
|
||||
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)
|
||||
|
||||
/*
|
||||
* relfrozenxid should never go backward. Caller can pass
|
||||
* InvalidTransactionId if it has no new data.
|
||||
*/
|
||||
if (TransactionIdIsNormal(frozenxid) &&
|
||||
TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid))
|
||||
{
|
||||
pgcform->relminxid = minxid;
|
||||
dirty = true;
|
||||
}
|
||||
if (TransactionIdIsValid(vacuumxid) && pgcform->relvacuumxid != vacuumxid)
|
||||
{
|
||||
pgcform->relvacuumxid = vacuumxid;
|
||||
pgcform->relfrozenxid = frozenxid;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@ -740,34 +725,41 @@ 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))
|
||||
/*
|
||||
* 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->datminxid = newMinXid;
|
||||
dirty = true;
|
||||
}
|
||||
if (TransactionIdPrecedes(dbform->datvacuumxid, newVacXid))
|
||||
{
|
||||
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.
|
||||
* Also update the XID wrap limit info 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.
|
||||
* The passed XID is simply the one I just wrote into my pg_database
|
||||
* entry. It's used to initialize the "min" calculation.
|
||||
*
|
||||
* 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.
|
||||
* 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));
|
||||
Assert(TransactionIdIsNormal(dbform->datfrozenxid));
|
||||
|
||||
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->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",
|
||||
|
@ -36,7 +36,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.80 2006/10/04 00:29:52 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.81 2006/11/05 22:42:08 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -78,7 +78,6 @@ 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 */
|
||||
@ -96,11 +95,13 @@ 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, TransactionId FreezeLimit,
|
||||
TransactionId OldestXmin);
|
||||
Relation *Irel, int nindexes);
|
||||
static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats);
|
||||
static void lazy_vacuum_index(Relation indrel,
|
||||
IndexBulkDeleteResult **stats,
|
||||
@ -110,10 +111,9 @@ 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,
|
||||
TransactionId OldestXmin);
|
||||
static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats);
|
||||
static BlockNumber count_nondeletable_pages(Relation onerel,
|
||||
LVRelStats *vacrelstats, TransactionId OldestXmin);
|
||||
LVRelStats *vacrelstats);
|
||||
static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks);
|
||||
static void lazy_record_dead_tuple(LVRelStats *vacrelstats,
|
||||
ItemPointer itemptr);
|
||||
@ -129,8 +129,7 @@ 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 relpages and reltuples statistics, as well as the
|
||||
* relminxid and relvacuumxid information.
|
||||
* updates its relpages and reltuples statistics.
|
||||
*
|
||||
* At entry, we have already established a transaction and opened
|
||||
* and locked the relation.
|
||||
@ -142,8 +141,6 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
|
||||
Relation *Irel;
|
||||
int nindexes;
|
||||
BlockNumber possibly_freeable;
|
||||
TransactionId OldestXmin,
|
||||
FreezeLimit;
|
||||
|
||||
if (vacstmt->verbose)
|
||||
elevel = INFO;
|
||||
@ -159,23 +156,12 @@ 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, RowExclusiveLock, &nindexes, &Irel);
|
||||
vacrelstats->hasindex = (nindexes > 0);
|
||||
|
||||
/* Do the vacuuming */
|
||||
lazy_scan_heap(onerel, vacrelstats, Irel, nindexes, FreezeLimit, OldestXmin);
|
||||
lazy_scan_heap(onerel, vacrelstats, Irel, nindexes);
|
||||
|
||||
/* Done with indexes */
|
||||
vac_close_indexes(nindexes, Irel, NoLock);
|
||||
@ -189,7 +175,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, OldestXmin);
|
||||
lazy_truncate_heap(onerel, vacrelstats);
|
||||
|
||||
/* Update shared free space map with final free space info */
|
||||
lazy_update_fsm(onerel, vacrelstats);
|
||||
@ -199,7 +185,7 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
|
||||
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,
|
||||
@ -215,16 +201,12 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
|
||||
* 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.
|
||||
*
|
||||
* If there are no indexes then we just vacuum each dirty page as we
|
||||
* process it, since there's no point in gathering many tuples.
|
||||
*/
|
||||
static void
|
||||
lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
|
||||
Relation *Irel, int nindexes, TransactionId FreezeLimit,
|
||||
TransactionId OldestXmin)
|
||||
Relation *Irel, int nindexes)
|
||||
{
|
||||
BlockNumber nblocks,
|
||||
blkno;
|
||||
@ -266,10 +248,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
|
||||
Page page;
|
||||
OffsetNumber offnum,
|
||||
maxoff;
|
||||
bool pgchanged,
|
||||
tupgone,
|
||||
bool tupgone,
|
||||
hastup;
|
||||
int prev_dead_count;
|
||||
OffsetNumber frozen[MaxOffsetNumber];
|
||||
int nfrozen;
|
||||
|
||||
vacuum_delay_point();
|
||||
|
||||
@ -293,7 +276,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
|
||||
|
||||
buf = ReadBuffer(onerel, blkno);
|
||||
|
||||
/* In this phase we only need shared access to the buffer */
|
||||
/* Initially, we only need shared access to the buffer */
|
||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||
|
||||
page = BufferGetPage(buf);
|
||||
@ -349,7 +332,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
|
||||
continue;
|
||||
}
|
||||
|
||||
pgchanged = false;
|
||||
nfrozen = 0;
|
||||
hastup = false;
|
||||
prev_dead_count = vacrelstats->num_dead_tuples;
|
||||
maxoff = PageGetMaxOffsetNumber(page);
|
||||
@ -379,31 +362,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
|
||||
tupgone = true; /* we can delete the tuple */
|
||||
break;
|
||||
case HEAPTUPLE_LIVE:
|
||||
|
||||
/*
|
||||
* Tuple is good. Consider whether to replace its xmin
|
||||
* value with FrozenTransactionId.
|
||||
*
|
||||
* NB: Since we hold only a shared buffer lock here, we
|
||||
* are assuming that TransactionId read/write is atomic.
|
||||
* This is not the only place that makes such an
|
||||
* assumption. It'd be possible to avoid the assumption by
|
||||
* momentarily acquiring exclusive lock, but for the
|
||||
* moment I see no need to.
|
||||
*/
|
||||
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",
|
||||
@ -435,22 +394,40 @@ 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.
|
||||
* Each non-removable tuple must be checked to see if it
|
||||
* needs freezing. If we already froze anything, then
|
||||
* we've already switched the buffer lock to exclusive.
|
||||
*/
|
||||
min = vactuple_get_minxid(&tuple);
|
||||
if (TransactionIdIsValid(min) &&
|
||||
TransactionIdPrecedes(min, vacrelstats->minxid))
|
||||
vacrelstats->minxid = min;
|
||||
if (heap_freeze_tuple(tuple.t_data, FreezeLimit,
|
||||
(nfrozen > 0) ? InvalidBuffer : buf))
|
||||
frozen[nfrozen++] = offnum;
|
||||
}
|
||||
} /* scan along page */
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If there are no indexes then we can vacuum the page right now
|
||||
* instead of doing a second scan.
|
||||
@ -485,8 +462,6 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
|
||||
if (hastup)
|
||||
vacrelstats->nonempty_pages = blkno + 1;
|
||||
|
||||
if (pgchanged)
|
||||
MarkBufferDirty(buf);
|
||||
UnlockReleaseBuffer(buf);
|
||||
}
|
||||
|
||||
@ -710,7 +685,7 @@ lazy_cleanup_index(Relation indrel,
|
||||
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",
|
||||
@ -731,8 +706,7 @@ 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,
|
||||
TransactionId OldestXmin)
|
||||
lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats)
|
||||
{
|
||||
BlockNumber old_rel_pages = vacrelstats->rel_pages;
|
||||
BlockNumber new_rel_pages;
|
||||
@ -773,7 +747,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, OldestXmin);
|
||||
new_rel_pages = count_nondeletable_pages(onerel, vacrelstats);
|
||||
|
||||
if (new_rel_pages >= old_rel_pages)
|
||||
{
|
||||
@ -837,8 +811,7 @@ 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,
|
||||
TransactionId OldestXmin)
|
||||
count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
|
||||
{
|
||||
BlockNumber blkno;
|
||||
HeapTupleData tuple;
|
||||
|
Reference in New Issue
Block a user