aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2006-11-05 22:42:10 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2006-11-05 22:42:10 +0000
commit48188e1621bb6711e7d092bee48523b18cd80177 (patch)
tree524459ab58f8740a23efa7b7b521833646c678ba /src
parent10c70b86023001dc6d9028737afc97850b86e58f (diff)
downloadpostgresql-48188e1621bb6711e7d092bee48523b18cd80177.tar.gz
postgresql-48188e1621bb6711e7d092bee48523b18cd80177.zip
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.
Diffstat (limited to 'src')
-rw-r--r--src/backend/access/heap/heapam.c297
-rw-r--r--src/backend/access/transam/clog.c74
-rw-r--r--src/backend/access/transam/rmgr.c4
-rw-r--r--src/backend/access/transam/varsup.c82
-rw-r--r--src/backend/access/transam/xact.c8
-rw-r--r--src/backend/access/transam/xlog.c32
-rw-r--r--src/backend/catalog/heap.c59
-rw-r--r--src/backend/commands/analyze.c6
-rw-r--r--src/backend/commands/dbcommands.c29
-rw-r--r--src/backend/commands/vacuum.c490
-rw-r--r--src/backend/commands/vacuumlazy.c123
-rw-r--r--src/backend/libpq/hba.c14
-rw-r--r--src/backend/nodes/copyfuncs.c4
-rw-r--r--src/backend/nodes/equalfuncs.c4
-rw-r--r--src/backend/parser/gram.y12
-rw-r--r--src/backend/postmaster/autovacuum.c415
-rw-r--r--src/backend/postmaster/postmaster.c30
-rw-r--r--src/backend/storage/ipc/procarray.c6
-rw-r--r--src/backend/utils/init/flatfiles.c36
-rw-r--r--src/backend/utils/init/postinit.c7
-rw-r--r--src/backend/utils/misc/guc.c20
-rw-r--r--src/backend/utils/misc/postgresql.conf.sample3
-rw-r--r--src/backend/utils/time/tqual.c10
-rw-r--r--src/include/access/clog.h3
-rw-r--r--src/include/access/heapam.h14
-rw-r--r--src/include/access/htup.h20
-rw-r--r--src/include/access/rmgr.h6
-rw-r--r--src/include/access/transam.h9
-rw-r--r--src/include/access/xlog.h3
-rw-r--r--src/include/catalog/catversion.h4
-rw-r--r--src/include/catalog/pg_attribute.h16
-rw-r--r--src/include/catalog/pg_autovacuum.h20
-rw-r--r--src/include/catalog/pg_class.h29
-rw-r--r--src/include/catalog/pg_database.h18
-rw-r--r--src/include/commands/vacuum.h10
-rw-r--r--src/include/libpq/hba.h5
-rw-r--r--src/include/nodes/parsenodes.h4
-rw-r--r--src/include/postmaster/autovacuum.h3
38 files changed, 1053 insertions, 876 deletions
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 57acaf2bb8c..12775cc2db7 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.220 2006/10/04 00:29:48 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.221 2006/11/05 22:42:07 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -2809,6 +2809,166 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
}
+/*
+ * heap_freeze_tuple
+ *
+ * Check to see whether any of the XID fields of a tuple (xmin, xmax, xvac)
+ * are older than the specified cutoff XID. If so, replace them with
+ * FrozenTransactionId or InvalidTransactionId as appropriate, and return
+ * TRUE. Return FALSE if nothing was changed.
+ *
+ * It is assumed that the caller has checked the tuple with
+ * HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD
+ * (else we should be removing the tuple, not freezing it).
+ *
+ * NB: cutoff_xid *must* be <= the current global xmin, to ensure that any
+ * XID older than it could neither be running nor seen as running by any
+ * open transaction. This ensures that the replacement will not change
+ * anyone's idea of the tuple state. Also, since we assume the tuple is
+ * not HEAPTUPLE_DEAD, the fact that an XID is not still running allows us
+ * to assume that it is either committed good or aborted, as appropriate;
+ * so we need no external state checks to decide what to do. (This is good
+ * because this function is applied during WAL recovery, when we don't have
+ * access to any such state, and can't depend on the hint bits to be set.)
+ *
+ * In lazy VACUUM, we call this while initially holding only a shared lock
+ * on the tuple's buffer. If any change is needed, we trade that in for an
+ * exclusive lock before making the change. Caller should pass the buffer ID
+ * if shared lock is held, InvalidBuffer if exclusive lock is already held.
+ *
+ * Note: it might seem we could make the changes without exclusive lock, since
+ * TransactionId read/write is assumed atomic anyway. However there is a race
+ * condition: someone who just fetched an old XID that we overwrite here could
+ * conceivably not finish checking the XID against pg_clog before we finish
+ * the VACUUM and perhaps truncate off the part of pg_clog he needs. Getting
+ * exclusive lock ensures no other backend is in process of checking the
+ * tuple status. Also, getting exclusive lock makes it safe to adjust the
+ * infomask bits.
+ */
+bool
+heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
+ Buffer buf)
+{
+ bool changed = false;
+ TransactionId xid;
+
+ xid = HeapTupleHeaderGetXmin(tuple);
+ if (TransactionIdIsNormal(xid) &&
+ TransactionIdPrecedes(xid, cutoff_xid))
+ {
+ if (buf != InvalidBuffer)
+ {
+ /* trade in share lock for exclusive lock */
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+ buf = InvalidBuffer;
+ }
+ HeapTupleHeaderSetXmin(tuple, FrozenTransactionId);
+ /*
+ * Might as well fix the hint bits too; usually XMIN_COMMITTED will
+ * already be set here, but there's a small chance not.
+ */
+ Assert(!(tuple->t_infomask & HEAP_XMIN_INVALID));
+ tuple->t_infomask |= HEAP_XMIN_COMMITTED;
+ changed = true;
+ }
+
+ /*
+ * When we release shared lock, it's possible for someone else to change
+ * xmax before we get the lock back, so repeat the check after acquiring
+ * exclusive lock. (We don't need this pushup for xmin, because only
+ * VACUUM could be interested in changing an existing tuple's xmin,
+ * and there's only one VACUUM allowed on a table at a time.)
+ */
+recheck_xmax:
+ if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI))
+ {
+ xid = HeapTupleHeaderGetXmax(tuple);
+ if (TransactionIdIsNormal(xid) &&
+ TransactionIdPrecedes(xid, cutoff_xid))
+ {
+ if (buf != InvalidBuffer)
+ {
+ /* trade in share lock for exclusive lock */
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+ buf = InvalidBuffer;
+ goto recheck_xmax; /* see comment above */
+ }
+ HeapTupleHeaderSetXmax(tuple, InvalidTransactionId);
+ /*
+ * The tuple might be marked either XMAX_INVALID or
+ * XMAX_COMMITTED + LOCKED. Normalize to INVALID just to be
+ * sure no one gets confused.
+ */
+ tuple->t_infomask &= ~HEAP_XMAX_COMMITTED;
+ tuple->t_infomask |= HEAP_XMAX_INVALID;
+ changed = true;
+ }
+ }
+ else
+ {
+ /*----------
+ * XXX perhaps someday we should zero out very old MultiXactIds here?
+ *
+ * The only way a stale MultiXactId could pose a problem is if a
+ * tuple, having once been multiply-share-locked, is not touched by
+ * any vacuum or attempted lock or deletion for just over 4G MultiXact
+ * creations, and then in the probably-narrow window where its xmax
+ * is again a live MultiXactId, someone tries to lock or delete it.
+ * Even then, another share-lock attempt would work fine. An
+ * exclusive-lock or delete attempt would face unexpected delay, or
+ * in the very worst case get a deadlock error. This seems an
+ * extremely low-probability scenario with minimal downside even if
+ * it does happen, so for now we don't do the extra bookkeeping that
+ * would be needed to clean out MultiXactIds.
+ *----------
+ */
+ }
+
+ /*
+ * Although xvac per se could only be set by VACUUM, it shares physical
+ * storage space with cmax, and so could be wiped out by someone setting
+ * xmax. Hence recheck after changing lock, same as for xmax itself.
+ */
+recheck_xvac:
+ if (tuple->t_infomask & HEAP_MOVED)
+ {
+ xid = HeapTupleHeaderGetXvac(tuple);
+ if (TransactionIdIsNormal(xid) &&
+ TransactionIdPrecedes(xid, cutoff_xid))
+ {
+ if (buf != InvalidBuffer)
+ {
+ /* trade in share lock for exclusive lock */
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+ buf = InvalidBuffer;
+ goto recheck_xvac; /* see comment above */
+ }
+ /*
+ * If a MOVED_OFF tuple is not dead, the xvac transaction must
+ * have failed; whereas a non-dead MOVED_IN tuple must mean the
+ * xvac transaction succeeded.
+ */
+ if (tuple->t_infomask & HEAP_MOVED_OFF)
+ HeapTupleHeaderSetXvac(tuple, InvalidTransactionId);
+ else
+ HeapTupleHeaderSetXvac(tuple, FrozenTransactionId);
+ /*
+ * Might as well fix the hint bits too; usually XMIN_COMMITTED will
+ * already be set here, but there's a small chance not.
+ */
+ Assert(!(tuple->t_infomask & HEAP_XMIN_INVALID));
+ tuple->t_infomask |= HEAP_XMIN_COMMITTED;
+ changed = true;
+ }
+ }
+
+ return changed;
+}
+
+
/* ----------------
* heap_markpos - mark scan position
* ----------------
@@ -2877,6 +3037,9 @@ heap_restrpos(HeapScanDesc scan)
/*
* Perform XLogInsert for a heap-clean operation. Caller must already
* have modified the buffer and marked it dirty.
+ *
+ * Note: for historical reasons, the entries in the unused[] array should
+ * be zero-based tuple indexes, not one-based.
*/
XLogRecPtr
log_heap_clean(Relation reln, Buffer buffer, OffsetNumber *unused, int uncnt)
@@ -2921,6 +3084,57 @@ log_heap_clean(Relation reln, Buffer buffer, OffsetNumber *unused, int uncnt)
}
/*
+ * Perform XLogInsert for a heap-freeze operation. Caller must already
+ * have modified the buffer and marked it dirty.
+ *
+ * Unlike log_heap_clean(), the offsets[] entries are one-based.
+ */
+XLogRecPtr
+log_heap_freeze(Relation reln, Buffer buffer,
+ TransactionId cutoff_xid,
+ OffsetNumber *offsets, int offcnt)
+{
+ xl_heap_freeze xlrec;
+ XLogRecPtr recptr;
+ XLogRecData rdata[2];
+
+ /* Caller should not call me on a temp relation */
+ Assert(!reln->rd_istemp);
+
+ xlrec.node = reln->rd_node;
+ xlrec.block = BufferGetBlockNumber(buffer);
+ xlrec.cutoff_xid = cutoff_xid;
+
+ rdata[0].data = (char *) &xlrec;
+ rdata[0].len = SizeOfHeapFreeze;
+ rdata[0].buffer = InvalidBuffer;
+ rdata[0].next = &(rdata[1]);
+
+ /*
+ * The tuple-offsets array is not actually in the buffer, but pretend
+ * that it is. When XLogInsert stores the whole buffer, the offsets array
+ * need not be stored too.
+ */
+ if (offcnt > 0)
+ {
+ rdata[1].data = (char *) offsets;
+ rdata[1].len = offcnt * sizeof(OffsetNumber);
+ }
+ else
+ {
+ rdata[1].data = NULL;
+ rdata[1].len = 0;
+ }
+ rdata[1].buffer = buffer;
+ rdata[1].buffer_std = true;
+ rdata[1].next = NULL;
+
+ recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_FREEZE, rdata);
+
+ return recptr;
+}
+
+/*
* Perform XLogInsert for a heap-update operation. Caller must already
* have modified the buffer(s) and marked them dirty.
*/
@@ -3057,6 +3271,7 @@ heap_xlog_clean(XLogRecPtr lsn, XLogRecord *record)
while (unused < unend)
{
+ /* unused[] entries are zero-based */
lp = PageGetItemId(page, *unused + 1);
lp->lp_flags &= ~LP_USED;
unused++;
@@ -3072,6 +3287,55 @@ heap_xlog_clean(XLogRecPtr lsn, XLogRecord *record)
}
static void
+heap_xlog_freeze(XLogRecPtr lsn, XLogRecord *record)
+{
+ xl_heap_freeze *xlrec = (xl_heap_freeze *) XLogRecGetData(record);
+ TransactionId cutoff_xid = xlrec->cutoff_xid;
+ Relation reln;
+ Buffer buffer;
+ Page page;
+
+ if (record->xl_info & XLR_BKP_BLOCK_1)
+ return;
+
+ reln = XLogOpenRelation(xlrec->node);
+ buffer = XLogReadBuffer(reln, xlrec->block, false);
+ if (!BufferIsValid(buffer))
+ return;
+ page = (Page) BufferGetPage(buffer);
+
+ if (XLByteLE(lsn, PageGetLSN(page)))
+ {
+ UnlockReleaseBuffer(buffer);
+ return;
+ }
+
+ if (record->xl_len > SizeOfHeapFreeze)
+ {
+ OffsetNumber *offsets;
+ OffsetNumber *offsets_end;
+
+ offsets = (OffsetNumber *) ((char *) xlrec + SizeOfHeapFreeze);
+ offsets_end = (OffsetNumber *) ((char *) xlrec + record->xl_len);
+
+ while (offsets < offsets_end)
+ {
+ /* offsets[] entries are one-based */
+ ItemId lp = PageGetItemId(page, *offsets);
+ HeapTupleHeader tuple = (HeapTupleHeader) PageGetItem(page, lp);
+
+ (void) heap_freeze_tuple(tuple, cutoff_xid, InvalidBuffer);
+ offsets++;
+ }
+ }
+
+ PageSetLSN(page, lsn);
+ PageSetTLI(page, ThisTimeLineID);
+ MarkBufferDirty(buffer);
+ UnlockReleaseBuffer(buffer);
+}
+
+static void
heap_xlog_newpage(XLogRecPtr lsn, XLogRecord *record)
{
xl_heap_newpage *xlrec = (xl_heap_newpage *) XLogRecGetData(record);
@@ -3546,6 +3810,18 @@ heap_redo(XLogRecPtr lsn, XLogRecord *record)
elog(PANIC, "heap_redo: unknown op code %u", info);
}
+void
+heap2_redo(XLogRecPtr lsn, XLogRecord *record)
+{
+ uint8 info = record->xl_info & ~XLR_INFO_MASK;
+
+ info &= XLOG_HEAP_OPMASK;
+ if (info == XLOG_HEAP2_FREEZE)
+ heap_xlog_freeze(lsn, record);
+ else
+ elog(PANIC, "heap2_redo: unknown op code %u", info);
+}
+
static void
out_target(StringInfo buf, xl_heaptid *target)
{
@@ -3645,3 +3921,22 @@ heap_desc(StringInfo buf, uint8 xl_info, char *rec)
else
appendStringInfo(buf, "UNKNOWN");
}
+
+void
+heap2_desc(StringInfo buf, uint8 xl_info, char *rec)
+{
+ uint8 info = xl_info & ~XLR_INFO_MASK;
+
+ info &= XLOG_HEAP_OPMASK;
+ if (info == XLOG_HEAP2_FREEZE)
+ {
+ xl_heap_freeze *xlrec = (xl_heap_freeze *) rec;
+
+ appendStringInfo(buf, "freeze: rel %u/%u/%u; blk %u; cutoff %u",
+ xlrec->node.spcNode, xlrec->node.dbNode,
+ xlrec->node.relNode, xlrec->block,
+ xlrec->cutoff_xid);
+ }
+ else
+ appendStringInfo(buf, "UNKNOWN");
+}
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index f57bdefa3a0..5817239a374 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -24,7 +24,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/access/transam/clog.c,v 1.40 2006/10/04 00:29:49 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/clog.c,v 1.41 2006/11/05 22:42:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -69,6 +69,7 @@ static SlruCtlData ClogCtlData;
static int ZeroCLOGPage(int pageno, bool writeXlog);
static bool CLOGPagePrecedes(int page1, int page2);
static void WriteZeroPageXlogRec(int pageno);
+static void WriteTruncateXlogRec(int pageno);
/*
@@ -309,16 +310,17 @@ ExtendCLOG(TransactionId newestXact)
/*
* Remove all CLOG segments before the one holding the passed transaction ID
*
- * When this is called, we know that the database logically contains no
- * reference to transaction IDs older than oldestXact. However, we must
- * not truncate the CLOG until we have performed a checkpoint, to ensure
- * that no such references remain on disk either; else a crash just after
- * the truncation might leave us with a problem. Since CLOG segments hold
- * a large number of transactions, the opportunity to actually remove a
- * segment is fairly rare, and so it seems best not to do the checkpoint
- * unless we have confirmed that there is a removable segment. Therefore
- * we issue the checkpoint command here, not in higher-level code as might
- * seem cleaner.
+ * Before removing any CLOG data, we must flush XLOG to disk, to ensure
+ * that any recently-emitted HEAP_FREEZE records have reached disk; otherwise
+ * a crash and restart might leave us with some unfrozen tuples referencing
+ * removed CLOG data. We choose to emit a special TRUNCATE XLOG record too.
+ * Replaying the deletion from XLOG is not critical, since the files could
+ * just as well be removed later, but doing so prevents a long-running hot
+ * standby server from acquiring an unreasonably bloated CLOG directory.
+ *
+ * Since CLOG segments hold a large number of transactions, the opportunity to
+ * actually remove a segment is fairly rare, and so it seems best not to do
+ * the XLOG flush unless we have confirmed that there is a removable segment.
*/
void
TruncateCLOG(TransactionId oldestXact)
@@ -335,8 +337,8 @@ TruncateCLOG(TransactionId oldestXact)
if (!SlruScanDirectory(ClogCtl, cutoffPage, false))
return; /* nothing to remove */
- /* Perform a CHECKPOINT */
- RequestCheckpoint(true, false);
+ /* Write XLOG record and flush XLOG to disk */
+ WriteTruncateXlogRec(cutoffPage);
/* Now we can remove the old CLOG segment(s) */
SimpleLruTruncate(ClogCtl, cutoffPage);
@@ -387,6 +389,29 @@ WriteZeroPageXlogRec(int pageno)
}
/*
+ * Write a TRUNCATE xlog record
+ *
+ * We must flush the xlog record to disk before returning --- see notes
+ * in TruncateCLOG().
+ *
+ * Note: xlog record is marked as outside transaction control, since we
+ * want it to be redone whether the invoking transaction commits or not.
+ */
+static void
+WriteTruncateXlogRec(int pageno)
+{
+ XLogRecData rdata;
+ XLogRecPtr recptr;
+
+ rdata.data = (char *) (&pageno);
+ rdata.len = sizeof(int);
+ rdata.buffer = InvalidBuffer;
+ rdata.next = NULL;
+ recptr = XLogInsert(RM_CLOG_ID, CLOG_TRUNCATE | XLOG_NO_TRAN, &rdata);
+ XLogFlush(recptr);
+}
+
+/*
* CLOG resource manager's routines
*/
void
@@ -409,6 +434,22 @@ clog_redo(XLogRecPtr lsn, XLogRecord *record)
LWLockRelease(CLogControlLock);
}
+ else if (info == CLOG_TRUNCATE)
+ {
+ int pageno;
+
+ memcpy(&pageno, XLogRecGetData(record), sizeof(int));
+
+ /*
+ * During XLOG replay, latest_page_number isn't set up yet; insert
+ * a suitable value to bypass the sanity test in SimpleLruTruncate.
+ */
+ ClogCtl->shared->latest_page_number = pageno;
+
+ SimpleLruTruncate(ClogCtl, pageno);
+ }
+ else
+ elog(PANIC, "clog_redo: unknown op code %u", info);
}
void
@@ -423,6 +464,13 @@ clog_desc(StringInfo buf, uint8 xl_info, char *rec)
memcpy(&pageno, rec, sizeof(int));
appendStringInfo(buf, "zeropage: %d", pageno);
}
+ else if (info == CLOG_TRUNCATE)
+ {
+ int pageno;
+
+ memcpy(&pageno, rec, sizeof(int));
+ appendStringInfo(buf, "truncate before: %d", pageno);
+ }
else
appendStringInfo(buf, "UNKNOWN");
}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 7db05c9c482..08de22eaa4a 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -3,7 +3,7 @@
*
* Resource managers definition
*
- * $PostgreSQL: pgsql/src/backend/access/transam/rmgr.c,v 1.24 2006/08/07 16:57:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/rmgr.c,v 1.25 2006/11/05 22:42:07 tgl Exp $
*/
#include "postgres.h"
@@ -32,7 +32,7 @@ const RmgrData RmgrTable[RM_MAX_ID + 1] = {
{"MultiXact", multixact_redo, multixact_desc, NULL, NULL, NULL},
{"Reserved 7", NULL, NULL, NULL, NULL, NULL},
{"Reserved 8", NULL, NULL, NULL, NULL, NULL},
- {"Reserved 9", NULL, NULL, NULL, NULL, NULL},
+ {"Heap2", heap2_redo, heap2_desc, NULL, NULL, NULL},
{"Heap", heap_redo, heap_desc, NULL, NULL, NULL},
{"Btree", btree_redo, btree_desc, btree_xlog_startup, btree_xlog_cleanup, btree_safe_restartpoint},
{"Hash", hash_redo, hash_desc, NULL, NULL, NULL},
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 04e9840cb5d..a8abaaea35c 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -6,7 +6,7 @@
* Copyright (c) 2000-2006, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.75 2006/10/04 00:29:49 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.76 2006/11/05 22:42:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -17,6 +17,8 @@
#include "access/subtrans.h"
#include "access/transam.h"
#include "miscadmin.h"
+#include "postmaster/autovacuum.h"
+#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "utils/builtins.h"
@@ -47,20 +49,31 @@ GetNewTransactionId(bool isSubXact)
xid = ShmemVariableCache->nextXid;
- /*
+ /*----------
* Check to see if it's safe to assign another XID. This protects against
* catastrophic data loss due to XID wraparound. The basic rules are:
- * warn if we're past xidWarnLimit, and refuse to execute transactions if
- * we're past xidStopLimit, unless we are running in a standalone backend
- * (which gives an escape hatch to the DBA who ignored all those
- * warnings).
+ *
+ * If we're past xidVacLimit, start trying to force autovacuum cycles.
+ * If we're past xidWarnLimit, start issuing warnings.
+ * If we're past xidStopLimit, refuse to execute transactions, unless
+ * we are running in a standalone backend (which gives an escape hatch
+ * to the DBA who somehow got past the earlier defenses).
*
* Test is coded to fall out as fast as possible during normal operation,
- * ie, when the warn limit is set and we haven't violated it.
+ * ie, when the vac limit is set and we haven't violated it.
+ *----------
*/
- if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidWarnLimit) &&
- TransactionIdIsValid(ShmemVariableCache->xidWarnLimit))
+ if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidVacLimit) &&
+ TransactionIdIsValid(ShmemVariableCache->xidVacLimit))
{
+ /*
+ * To avoid swamping the postmaster with signals, we issue the
+ * autovac request only once per 64K transaction starts. This
+ * still gives plenty of chances before we get into real trouble.
+ */
+ if (IsUnderPostmaster && (xid % 65536) == 0)
+ SendPostmasterSignal(PMSIGNAL_START_AUTOVAC);
+
if (IsUnderPostmaster &&
TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidStopLimit))
ereport(ERROR,
@@ -69,7 +82,7 @@ GetNewTransactionId(bool isSubXact)
NameStr(ShmemVariableCache->limit_datname)),
errhint("Stop the postmaster and use a standalone backend to vacuum database \"%s\".",
NameStr(ShmemVariableCache->limit_datname))));
- else
+ else if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidWarnLimit))
ereport(WARNING,
(errmsg("database \"%s\" must be vacuumed within %u transactions",
NameStr(ShmemVariableCache->limit_datname),
@@ -178,28 +191,29 @@ ReadNewTransactionId(void)
/*
* Determine the last safe XID to allocate given the currently oldest
- * datminxid (ie, the oldest XID that might exist in any database
+ * datfrozenxid (ie, the oldest XID that might exist in any database
* of our cluster).
*/
void
-SetTransactionIdLimit(TransactionId oldest_datminxid,
+SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
Name oldest_datname)
{
+ TransactionId xidVacLimit;
TransactionId xidWarnLimit;
TransactionId xidStopLimit;
TransactionId xidWrapLimit;
TransactionId curXid;
- Assert(TransactionIdIsValid(oldest_datminxid));
+ Assert(TransactionIdIsNormal(oldest_datfrozenxid));
/*
* The place where we actually get into deep trouble is halfway around
- * from the oldest existing XID. (This calculation is probably off by one
- * or two counts, because the special XIDs reduce the size of the loop a
- * little bit. But we throw in plenty of slop below, so it doesn't
- * matter.)
+ * from the oldest potentially-existing XID. (This calculation is
+ * probably off by one or two counts, because the special XIDs reduce the
+ * size of the loop a little bit. But we throw in plenty of slop below,
+ * so it doesn't matter.)
*/
- xidWrapLimit = oldest_datminxid + (MaxTransactionId >> 1);
+ xidWrapLimit = oldest_datfrozenxid + (MaxTransactionId >> 1);
if (xidWrapLimit < FirstNormalTransactionId)
xidWrapLimit += FirstNormalTransactionId;
@@ -229,8 +243,28 @@ SetTransactionIdLimit(TransactionId oldest_datminxid,
if (xidWarnLimit < FirstNormalTransactionId)
xidWarnLimit -= FirstNormalTransactionId;
+ /*
+ * We'll start trying to force autovacuums when oldest_datfrozenxid
+ * gets to be more than autovacuum_freeze_max_age transactions old.
+ *
+ * Note: guc.c ensures that autovacuum_freeze_max_age is in a sane
+ * range, so that xidVacLimit will be well before xidWarnLimit.
+ *
+ * Note: autovacuum_freeze_max_age is a PGC_POSTMASTER parameter so that
+ * we don't have to worry about dealing with on-the-fly changes in its
+ * value. It doesn't look practical to update shared state from a GUC
+ * assign hook (too many processes would try to execute the hook,
+ * resulting in race conditions as well as crashes of those not
+ * connected to shared memory). Perhaps this can be improved someday.
+ */
+ xidVacLimit = oldest_datfrozenxid + autovacuum_freeze_max_age;
+ if (xidVacLimit < FirstNormalTransactionId)
+ xidVacLimit += FirstNormalTransactionId;
+
/* Grab lock for just long enough to set the new limit values */
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
+ ShmemVariableCache->oldestXid = oldest_datfrozenxid;
+ ShmemVariableCache->xidVacLimit = xidVacLimit;
ShmemVariableCache->xidWarnLimit = xidWarnLimit;
ShmemVariableCache->xidStopLimit = xidStopLimit;
ShmemVariableCache->xidWrapLimit = xidWrapLimit;
@@ -242,6 +276,18 @@ SetTransactionIdLimit(TransactionId oldest_datminxid,
ereport(DEBUG1,
(errmsg("transaction ID wrap limit is %u, limited by database \"%s\"",
xidWrapLimit, NameStr(*oldest_datname))));
+
+ /*
+ * If past the autovacuum force point, immediately signal an autovac
+ * request. The reason for this is that autovac only processes one
+ * database per invocation. Once it's finished cleaning up the oldest
+ * database, it'll call here, and we'll signal the postmaster to start
+ * another iteration immediately if there are still any old databases.
+ */
+ if (TransactionIdFollowsOrEquals(curXid, xidVacLimit) &&
+ IsUnderPostmaster)
+ SendPostmasterSignal(PMSIGNAL_START_AUTOVAC);
+
/* Give an immediate warning if past the wrap warn point */
if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit))
ereport(WARNING,
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 8e1724989cb..3c6e2ebf5cd 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.227 2006/10/04 00:29:49 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.228 2006/11/05 22:42:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -468,8 +468,12 @@ TransactionIdIsCurrentTransactionId(TransactionId xid)
* is what we need during bootstrap. (Bootstrap mode only inserts tuples,
* it never updates or deletes them, so all tuples can be presumed good
* immediately.)
+ *
+ * Likewise, InvalidTransactionId and FrozenTransactionId are certainly
+ * not my transaction ID, so we can just return "false" immediately for
+ * any non-normal XID.
*/
- if (xid == BootstrapTransactionId)
+ if (!TransactionIdIsNormal(xid))
return false;
/*
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index ba029397f87..03440cbf48b 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/access/transam/xlog.c,v 1.252 2006/10/18 22:44:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/xlog.c,v 1.253 2006/11/05 22:42:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -5344,36 +5344,6 @@ GetLastSegSwitchTime(void)
}
/*
- * GetRecentNextXid - get the nextXid value saved by the most recent checkpoint
- *
- * This is currently used only by the autovacuum daemon. To check for
- * impending XID wraparound, autovac needs an approximate idea of the current
- * XID counter, and it needs it before choosing which DB to attach to, hence
- * before it sets up a PGPROC, hence before it can take any LWLocks. But it
- * has attached to shared memory, and so we can let it reach into the shared
- * ControlFile structure and pull out the last checkpoint nextXID.
- *
- * Since we don't take any sort of lock, we have to assume that reading a
- * TransactionId is atomic ... but that assumption is made elsewhere, too,
- * and in any case the worst possible consequence of a bogus result is that
- * autovac issues an unnecessary database-wide VACUUM.
- *
- * Note: we could also choose to read ShmemVariableCache->nextXid in an
- * unlocked fashion, thus getting a more up-to-date result; but since that
- * changes far more frequently than the controlfile checkpoint copy, it would
- * pose a far higher risk of bogus result if we did have a nonatomic-read
- * problem.
- *
- * A (theoretically) completely safe answer is to read the actual pg_control
- * file into local process memory, but that certainly seems like overkill.
- */
-TransactionId
-GetRecentNextXid(void)
-{
- return ControlFile->checkPointCopy.nextXid;
-}
-
-/*
* GetNextXidAndEpoch - get the current nextXid value and associated epoch
*
* This is exported for use by code that would like to have 64-bit XIDs.
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d30556d48c1..d6822c73c69 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.313 2006/10/04 00:29:50 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.314 2006/11/05 22:42:08 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -595,8 +595,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
- values[Anum_pg_class_relminxid - 1] = TransactionIdGetDatum(rd_rel->relminxid);
- values[Anum_pg_class_relvacuumxid - 1] = TransactionIdGetDatum(rd_rel->relvacuumxid);
+ values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
/* start out with empty permissions */
nulls[Anum_pg_class_relacl - 1] = 'n';
if (reloptions != (Datum) 0)
@@ -644,35 +643,6 @@ AddNewRelationTuple(Relation pg_class_desc,
*/
new_rel_reltup = new_rel_desc->rd_rel;
- /* Initialize relminxid and relvacuumxid */
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_TOASTVALUE)
- {
- /*
- * Only real tables have Xids stored in them; initialize our known
- * value to the minimum Xid that could put tuples in the new table.
- */
- if (!IsBootstrapProcessingMode())
- {
- new_rel_reltup->relminxid = RecentXmin;
- new_rel_reltup->relvacuumxid = RecentXmin;
- }
- else
- {
- new_rel_reltup->relminxid = FirstNormalTransactionId;
- new_rel_reltup->relvacuumxid = FirstNormalTransactionId;
- }
- }
- else
- {
- /*
- * Other relations will not have Xids in them, so set the initial
- * value to InvalidTransactionId.
- */
- new_rel_reltup->relminxid = InvalidTransactionId;
- new_rel_reltup->relvacuumxid = InvalidTransactionId;
- }
-
switch (relkind)
{
case RELKIND_RELATION:
@@ -694,6 +664,31 @@ AddNewRelationTuple(Relation pg_class_desc,
break;
}
+ /* Initialize relfrozenxid */
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_TOASTVALUE)
+ {
+ /*
+ * Initialize to the minimum XID that could put tuples in the table.
+ * We know that no xacts older than RecentXmin are still running,
+ * so that will do.
+ */
+ if (!IsBootstrapProcessingMode())
+ new_rel_reltup->relfrozenxid = RecentXmin;
+ else
+ new_rel_reltup->relfrozenxid = FirstNormalTransactionId;
+ }
+ else
+ {
+ /*
+ * Other relation types will not contain XIDs, so set relfrozenxid
+ * to InvalidTransactionId. (Note: a sequence does contain a tuple,
+ * but we force its xmin to be FrozenTransactionId always; see
+ * commands/sequence.c.)
+ */
+ new_rel_reltup->relfrozenxid = InvalidTransactionId;
+ }
+
new_rel_reltup->relowner = relowner;
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->relkind = relkind;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index cb538bdd42b..95410258d34 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.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 */
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index cd327bb8d15..566d559c78a 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.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;
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",
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index c89dc20404c..b57ce14a700 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -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,23 +394,41 @@ 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;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 776d167ff2e..cfacd208c6b 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.156 2006/10/04 00:29:53 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.157 2006/11/05 22:42:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1004,16 +1004,14 @@ load_hba(void)
* dbname: gets database name (must be of size NAMEDATALEN bytes)
* dboid: gets database OID
* dbtablespace: gets database's default tablespace's OID
- * dbminxid: gets database's minimum XID
- * dbvacuumxid: gets database's vacuum XID
+ * dbfrozenxid: gets database's frozen XID
*
* This is not much related to the other functions in hba.c, but we put it
* here because it uses the next_token() infrastructure.
*/
bool
read_pg_database_line(FILE *fp, char *dbname, Oid *dboid,
- Oid *dbtablespace, TransactionId *dbminxid,
- TransactionId *dbvacuumxid)
+ Oid *dbtablespace, TransactionId *dbfrozenxid)
{
char buf[MAX_TOKEN];
@@ -1035,11 +1033,7 @@ read_pg_database_line(FILE *fp, char *dbname, Oid *dboid,
next_token(fp, buf, sizeof(buf));
if (!isdigit((unsigned char) buf[0]))
elog(FATAL, "bad data in flat pg_database file");
- *dbminxid = atoxid(buf);
- next_token(fp, buf, sizeof(buf));
- if (!isdigit((unsigned char) buf[0]))
- elog(FATAL, "bad data in flat pg_database file");
- *dbvacuumxid = atoxid(buf);
+ *dbfrozenxid = atoxid(buf);
/* expect EOL next */
if (next_token(fp, buf, sizeof(buf)))
elog(FATAL, "bad data in flat pg_database file");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 8efe73904c0..c1858e6746d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.352 2006/10/13 21:43:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.353 2006/11/05 22:42:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -2350,8 +2350,8 @@ _copyVacuumStmt(VacuumStmt *from)
COPY_SCALAR_FIELD(vacuum);
COPY_SCALAR_FIELD(full);
COPY_SCALAR_FIELD(analyze);
- COPY_SCALAR_FIELD(freeze);
COPY_SCALAR_FIELD(verbose);
+ COPY_SCALAR_FIELD(freeze_min_age);
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0ac818f8a29..a42afb77a3d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.286 2006/10/13 21:43:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.287 2006/11/05 22:42:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1215,8 +1215,8 @@ _equalVacuumStmt(VacuumStmt *a, VacuumStmt *b)
COMPARE_SCALAR_FIELD(vacuum);
COMPARE_SCALAR_FIELD(full);
COMPARE_SCALAR_FIELD(analyze);
- COMPARE_SCALAR_FIELD(freeze);
COMPARE_SCALAR_FIELD(verbose);
+ COMPARE_SCALAR_FIELD(freeze_min_age);
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1d1e105c74e..c90743a1017 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.567 2006/10/13 21:43:19 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.568 2006/11/05 22:42:09 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -5158,7 +5158,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->vacuum = true;
n->analyze = false;
n->full = $2;
- n->freeze = $3;
+ n->freeze_min_age = $3 ? 0 : -1;
n->verbose = $4;
n->relation = NULL;
n->va_cols = NIL;
@@ -5170,7 +5170,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->vacuum = true;
n->analyze = false;
n->full = $2;
- n->freeze = $3;
+ n->freeze_min_age = $3 ? 0 : -1;
n->verbose = $4;
n->relation = $5;
n->va_cols = NIL;
@@ -5181,7 +5181,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
VacuumStmt *n = (VacuumStmt *) $5;
n->vacuum = true;
n->full = $2;
- n->freeze = $3;
+ n->freeze_min_age = $3 ? 0 : -1;
n->verbose |= $4;
$$ = (Node *)n;
}
@@ -5194,7 +5194,7 @@ AnalyzeStmt:
n->vacuum = false;
n->analyze = true;
n->full = false;
- n->freeze = false;
+ n->freeze_min_age = -1;
n->verbose = $2;
n->relation = NULL;
n->va_cols = NIL;
@@ -5206,7 +5206,7 @@ AnalyzeStmt:
n->vacuum = false;
n->analyze = true;
n->full = false;
- n->freeze = false;
+ n->freeze_min_age = -1;
n->verbose = $2;
n->relation = $3;
n->va_cols = $4;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 2ba12c2f9e6..11552c94644 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.27 2006/10/04 00:29:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.28 2006/11/05 22:42:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -59,6 +59,7 @@ int autovacuum_vac_thresh;
double autovacuum_vac_scale;
int autovacuum_anl_thresh;
double autovacuum_anl_scale;
+int autovacuum_freeze_max_age;
int autovacuum_vac_cost_delay;
int autovacuum_vac_cost_limit;
@@ -70,6 +71,12 @@ static bool am_autovacuum = false;
static time_t last_autovac_start_time = 0;
static time_t last_autovac_stop_time = 0;
+/* Comparison point for determining whether freeze_max_age is exceeded */
+static TransactionId recentXid;
+
+/* Default freeze_min_age to use for autovacuum (varies by database) */
+static int default_freeze_min_age;
+
/* Memory context for long-lived data */
static MemoryContext AutovacMemCxt;
@@ -78,10 +85,8 @@ typedef struct autovac_dbase
{
Oid oid;
char *name;
- TransactionId minxid;
- TransactionId vacuumxid;
+ TransactionId frozenxid;
PgStat_StatDBEntry *entry;
- int32 age;
} autovac_dbase;
/* struct to keep track of tables to vacuum and/or analyze */
@@ -91,6 +96,7 @@ typedef struct autovac_table
Oid toastrelid;
bool dovacuum;
bool doanalyze;
+ int freeze_min_age;
int vacuum_cost_delay;
int vacuum_cost_limit;
} autovac_table;
@@ -100,7 +106,6 @@ typedef struct autovac_table
static pid_t autovac_forkexec(void);
#endif
NON_EXEC_STATIC void AutoVacMain(int argc, char *argv[]);
-static void process_whole_db(void);
static void do_autovacuum(PgStat_StatDBEntry *dbentry);
static List *autovac_get_database_list(void);
static void test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
@@ -108,10 +113,9 @@ static void test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
Form_pg_autovacuum avForm,
List **vacuum_tables,
List **toast_table_ids);
-static void autovacuum_do_vac_analyze(List *relids, bool dovacuum,
- bool doanalyze, bool freeze);
-static void autovac_report_activity(VacuumStmt *vacstmt,
- List *relids);
+static void autovacuum_do_vac_analyze(Oid relid, bool dovacuum,
+ bool doanalyze, int freeze_min_age);
+static void autovac_report_activity(VacuumStmt *vacstmt, Oid relid);
/*
@@ -222,9 +226,9 @@ AutoVacMain(int argc, char *argv[])
{
ListCell *cell;
List *dblist;
- TransactionId nextXid;
autovac_dbase *db;
- bool whole_db;
+ TransactionId xidForceLimit;
+ bool for_xid_wrap;
sigjmp_buf local_sigjmp_buf;
/* we are a postmaster subprocess now */
@@ -315,29 +319,28 @@ AutoVacMain(int argc, char *argv[])
dblist = autovac_get_database_list();
/*
- * Get the next Xid that was current as of the last checkpoint. We need it
- * to determine whether databases are about to need database-wide vacuums.
+ * Determine the oldest datfrozenxid/relfrozenxid that we will allow
+ * to pass without forcing a vacuum. (This limit can be tightened for
+ * particular tables, but not loosened.)
*/
- nextXid = GetRecentNextXid();
+ recentXid = ReadNewTransactionId();
+ xidForceLimit = recentXid - autovacuum_freeze_max_age;
+ /* ensure it's a "normal" XID, else TransactionIdPrecedes misbehaves */
+ if (xidForceLimit < FirstNormalTransactionId)
+ xidForceLimit -= FirstNormalTransactionId;
/*
* Choose a database to connect to. We pick the database that was least
- * recently auto-vacuumed, or one that needs database-wide vacuum (to
- * prevent Xid wraparound-related data loss).
+ * recently auto-vacuumed, or one that needs vacuuming to prevent Xid
+ * wraparound-related data loss. If any db at risk of wraparound is
+ * found, we pick the one with oldest datfrozenxid,
+ * independently of autovacuum times.
*
* Note that a database with no stats entry is not considered, except for
* Xid wraparound purposes. The theory is that if no one has ever
* connected to it since the stats were last initialized, it doesn't need
* vacuuming.
*
- * Note that if we are called when autovacuum is nominally disabled in
- * postgresql.conf, we assume the postmaster has invoked us because a
- * database is in danger of Xid wraparound. In that case, we only
- * consider vacuuming whole databases, not individual tables; and we pick
- * the oldest one, regardless of it's true age. So the criteria for
- * deciding that a database needs a database-wide vacuum is elsewhere
- * (currently in vac_truncate_clog).
- *
* XXX This could be improved if we had more info about whether it needs
* vacuuming before connecting to it. Perhaps look through the pgstats
* data for the database's tables? One idea is to keep track of the
@@ -346,84 +349,40 @@ AutoVacMain(int argc, char *argv[])
* starvation for less busy databases.
*/
db = NULL;
- whole_db = false;
-
- if (AutoVacuumingActive())
+ for_xid_wrap = false;
+ foreach(cell, dblist)
{
- /*
- * We look for the database that most urgently needs a database-wide
- * vacuum. We decide that a database-wide vacuum is needed 100000
- * transactions sooner than vacuum.c's vac_truncate_clog() would
- * decide to start giving warnings. If any such db is found, we
- * ignore all other dbs.
- *
- * Unlike vacuum.c, we also look at vacuumxid. This is so that
- * pg_clog can be kept trimmed to a reasonable size.
- */
- foreach(cell, dblist)
+ autovac_dbase *tmp = lfirst(cell);
+
+ /* Find pgstat entry if any */
+ tmp->entry = pgstat_fetch_stat_dbentry(tmp->oid);
+
+ /* Check to see if this one is at risk of wraparound */
+ if (TransactionIdPrecedes(tmp->frozenxid, xidForceLimit))
{
- autovac_dbase *tmp = lfirst(cell);
- bool this_whole_db;
- int32 true_age,
- vacuum_age;
-
- true_age = (int32) (nextXid - tmp->minxid);
- vacuum_age = (int32) (nextXid - tmp->vacuumxid);
- tmp->age = Max(true_age, vacuum_age);
-
- this_whole_db = (tmp->age >
- (int32) ((MaxTransactionId >> 3) * 3 - 100000));
-
- if (whole_db || this_whole_db)
- {
- if (!this_whole_db)
- continue;
- if (db == NULL || tmp->age > db->age)
- {
- db = tmp;
- whole_db = true;
- }
- continue;
- }
-
- /*
- * Otherwise, skip a database with no pgstat entry; it means it
- * hasn't seen any activity.
- */
- tmp->entry = pgstat_fetch_stat_dbentry(tmp->oid);
- if (!tmp->entry)
- continue;
-
- /*
- * Remember the db with oldest autovac time.
- */
if (db == NULL ||
- tmp->entry->last_autovac_time < db->entry->last_autovac_time)
+ TransactionIdPrecedes(tmp->frozenxid, db->frozenxid))
db = tmp;
+ for_xid_wrap = true;
+ continue;
}
- }
- else
- {
+ else if (for_xid_wrap)
+ continue; /* ignore not-at-risk DBs */
+
/*
- * If autovacuuming is not active, we must have gotten here because a
- * backend signalled the postmaster. Pick up the database with the
- * greatest age, and apply a database-wide vacuum on it.
+ * Otherwise, skip a database with no pgstat entry; it means it
+ * hasn't seen any activity.
*/
- int32 oldest = 0;
-
- whole_db = true;
- foreach(cell, dblist)
- {
- autovac_dbase *tmp = lfirst(cell);
- int32 age = (int32) (nextXid - tmp->minxid);
+ if (!tmp->entry)
+ continue;
- if (age > oldest)
- {
- oldest = age;
- db = tmp;
- }
- }
- Assert(db);
+ /*
+ * Remember the db with oldest autovac time. (If we are here,
+ * both tmp->entry and db->entry must be non-null.)
+ */
+ if (db == NULL ||
+ tmp->entry->last_autovac_time < db->entry->last_autovac_time)
+ db = tmp;
}
if (db)
@@ -460,10 +419,7 @@ AutoVacMain(int argc, char *argv[])
/*
* And do an appropriate amount of work
*/
- if (whole_db)
- process_whole_db();
- else
- do_autovacuum(db->entry);
+ do_autovacuum(db->entry);
}
/* One iteration done, go away */
@@ -485,8 +441,7 @@ autovac_get_database_list(void)
FILE *db_file;
Oid db_id;
Oid db_tablespace;
- TransactionId db_minxid;
- TransactionId db_vacuumxid;
+ TransactionId db_frozenxid;
filename = database_getflatfilename();
db_file = AllocateFile(filename, "r");
@@ -496,8 +451,7 @@ autovac_get_database_list(void)
errmsg("could not open file \"%s\": %m", filename)));
while (read_pg_database_line(db_file, thisname, &db_id,
- &db_tablespace, &db_minxid,
- &db_vacuumxid))
+ &db_tablespace, &db_frozenxid))
{
autovac_dbase *db;
@@ -505,11 +459,9 @@ autovac_get_database_list(void)
db->oid = db_id;
db->name = pstrdup(thisname);
- db->minxid = db_minxid;
- db->vacuumxid = db_vacuumxid;
- /* these get set later: */
+ db->frozenxid = db_frozenxid;
+ /* this gets set later: */
db->entry = NULL;
- db->age = 0;
dblist = lappend(dblist, db);
}
@@ -521,59 +473,11 @@ autovac_get_database_list(void)
}
/*
- * Process a whole database. If it's a template database or is disallowing
- * connection by means of datallowconn=false, then issue a VACUUM FREEZE.
- * Else use a plain VACUUM.
- */
-static void
-process_whole_db(void)
-{
- HeapTuple tup;
- Form_pg_database dbForm;
- bool freeze;
-
- /* Start a transaction so our commands have one to play into. */
- StartTransactionCommand();
-
- /* functions in indexes may want a snapshot set */
- ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
-
- /*
- * Clean up any dead statistics collector entries for this DB.
- */
- pgstat_vacuum_tabstat();
-
- /* Look up the pg_database entry and decide whether to FREEZE */
- tup = SearchSysCache(DATABASEOID,
- ObjectIdGetDatum(MyDatabaseId),
- 0, 0, 0);
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
-
- dbForm = (Form_pg_database) GETSTRUCT(tup);
-
- if (!dbForm->datallowconn || dbForm->datistemplate)
- freeze = true;
- else
- freeze = false;
-
- ReleaseSysCache(tup);
-
- elog(DEBUG2, "autovacuum: VACUUM%s whole database",
- (freeze) ? " FREEZE" : "");
-
- autovacuum_do_vac_analyze(NIL, true, false, freeze);
-
- /* Finally close out the last transaction. */
- CommitTransactionCommand();
-}
-
-/*
* Process a database table-by-table
*
- * dbentry must be a valid pointer to the database entry in the stats
- * databases' hash table, and it will be used to determine whether vacuum or
- * analyze is needed on a per-table basis.
+ * dbentry is either a pointer to the database entry in the stats databases
+ * hash table, or NULL if we couldn't find any entry (the latter case occurs
+ * only if we are forcing a vacuum for anti-wrap purposes).
*
* Note that CHECK_FOR_INTERRUPTS is supposed to be used in certain spots in
* order not to ignore shutdown commands for too long.
@@ -585,6 +489,7 @@ do_autovacuum(PgStat_StatDBEntry *dbentry)
avRel;
HeapTuple tuple;
HeapScanDesc relScan;
+ Form_pg_database dbForm;
List *vacuum_tables = NIL;
List *toast_table_ids = NIL;
ListCell *cell;
@@ -604,6 +509,25 @@ do_autovacuum(PgStat_StatDBEntry *dbentry)
pgstat_vacuum_tabstat();
/*
+ * Find the pg_database entry and select the default freeze_min_age.
+ * We use zero in template and nonconnectable databases,
+ * else the system-wide default.
+ */
+ tuple = SearchSysCache(DATABASEOID,
+ ObjectIdGetDatum(MyDatabaseId),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
+ dbForm = (Form_pg_database) GETSTRUCT(tuple);
+
+ if (dbForm->datistemplate || !dbForm->datallowconn)
+ default_freeze_min_age = 0;
+ else
+ default_freeze_min_age = vacuum_freeze_min_age;
+
+ ReleaseSysCache(tuple);
+
+ /*
* StartTransactionCommand and CommitTransactionCommand will automatically
* switch to other contexts. We need this one to keep the list of
* relations to vacuum/analyze across transactions.
@@ -676,9 +600,11 @@ do_autovacuum(PgStat_StatDBEntry *dbentry)
if (classForm->relisshared && PointerIsValid(shared))
tabentry = hash_search(shared->tables, &relid,
HASH_FIND, NULL);
- else
+ else if (PointerIsValid(dbentry))
tabentry = hash_search(dbentry->tables, &relid,
HASH_FIND, NULL);
+ else
+ tabentry = NULL;
test_rel_for_autovac(relid, tabentry, classForm, avForm,
&vacuum_tables, &toast_table_ids);
@@ -719,12 +645,18 @@ do_autovacuum(PgStat_StatDBEntry *dbentry)
VacuumCostDelay = tab->vacuum_cost_delay;
VacuumCostLimit = tab->vacuum_cost_limit;
- autovacuum_do_vac_analyze(list_make1_oid(tab->relid),
+ autovacuum_do_vac_analyze(tab->relid,
tab->dovacuum,
tab->doanalyze,
- false);
+ tab->freeze_min_age);
}
+ /*
+ * Update pg_database.datfrozenxid, and truncate pg_clog if possible.
+ * We only need to do this once, not after each table.
+ */
+ vac_update_datfrozenxid();
+
/* Finally close out the last transaction. */
CommitTransactionCommand();
}
@@ -746,10 +678,13 @@ do_autovacuum(PgStat_StatDBEntry *dbentry)
* the number of tuples (both live and dead) that there were as of the last
* analyze. This is asymmetric to the VACUUM case.
*
+ * We also force vacuum if the table's relfrozenxid is more than freeze_max_age
+ * transactions back.
+ *
* A table whose pg_autovacuum.enabled value is false, is automatically
- * skipped. Thus autovacuum can be disabled for specific tables. Also,
- * when the stats collector does not have data about a table, it will be
- * skipped.
+ * skipped (unless we have to vacuum it due to freeze_max_age). Thus
+ * autovacuum can be disabled for specific tables. Also, when the stats
+ * collector does not have data about a table, it will be skipped.
*
* A table whose vac_base_thresh value is <0 takes the base value from the
* autovacuum_vacuum_threshold GUC variable. Similarly, a vac_scale_factor
@@ -763,44 +698,28 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
List **vacuum_tables,
List **toast_table_ids)
{
+ bool force_vacuum;
+ bool dovacuum;
+ bool doanalyze;
float4 reltuples; /* pg_class.reltuples */
-
/* constants from pg_autovacuum or GUC variables */
int vac_base_thresh,
anl_base_thresh;
float4 vac_scale_factor,
anl_scale_factor;
-
/* thresholds calculated from above constants */
float4 vacthresh,
anlthresh;
-
/* number of vacuum (resp. analyze) tuples at this time */
float4 vactuples,
anltuples;
-
+ /* freeze parameters */
+ int freeze_min_age;
+ int freeze_max_age;
+ TransactionId xidForceLimit;
/* cost-based vacuum delay parameters */
int vac_cost_limit;
int vac_cost_delay;
- bool dovacuum;
- bool doanalyze;
-
- /* User disabled it in pg_autovacuum? */
- if (avForm && !avForm->enabled)
- return;
-
- /*
- * Skip a table not found in stat hash. If it's not acted upon, there's
- * no need to vacuum it. (Note that database-level check will take care
- * of Xid wraparound.)
- */
- if (!PointerIsValid(tabentry))
- return;
-
- reltuples = classForm->reltuples;
- vactuples = tabentry->n_dead_tuples;
- anltuples = tabentry->n_live_tuples + tabentry->n_dead_tuples -
- tabentry->last_anl_tuples;
/*
* If there is a tuple in pg_autovacuum, use it; else, use the GUC
@@ -819,6 +738,12 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
anl_base_thresh = (avForm->anl_base_thresh >= 0) ?
avForm->anl_base_thresh : autovacuum_anl_thresh;
+ freeze_min_age = (avForm->freeze_min_age >= 0) ?
+ avForm->freeze_min_age : default_freeze_min_age;
+ freeze_max_age = (avForm->freeze_max_age >= 0) ?
+ Min(avForm->freeze_max_age, autovacuum_freeze_max_age) :
+ autovacuum_freeze_max_age;
+
vac_cost_limit = (avForm->vac_cost_limit >= 0) ?
avForm->vac_cost_limit :
((autovacuum_vac_cost_limit >= 0) ?
@@ -837,6 +762,9 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
anl_scale_factor = autovacuum_anl_scale;
anl_base_thresh = autovacuum_anl_thresh;
+ freeze_min_age = default_freeze_min_age;
+ freeze_max_age = autovacuum_freeze_max_age;
+
vac_cost_limit = (autovacuum_vac_cost_limit >= 0) ?
autovacuum_vac_cost_limit : VacuumCostLimit;
@@ -844,22 +772,51 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
autovacuum_vac_cost_delay : VacuumCostDelay;
}
- vacthresh = (float4) vac_base_thresh + vac_scale_factor * reltuples;
- anlthresh = (float4) anl_base_thresh + anl_scale_factor * reltuples;
+ /* Force vacuum if table is at risk of wraparound */
+ xidForceLimit = recentXid - freeze_max_age;
+ if (xidForceLimit < FirstNormalTransactionId)
+ xidForceLimit -= FirstNormalTransactionId;
+ force_vacuum = (TransactionIdIsNormal(classForm->relfrozenxid) &&
+ TransactionIdPrecedes(classForm->relfrozenxid,
+ xidForceLimit));
- /*
- * Note that we don't need to take special consideration for stat reset,
- * because if that happens, the last vacuum and analyze counts will be
- * reset too.
- */
+ /* User disabled it in pg_autovacuum? (But ignore if at risk) */
+ if (avForm && !avForm->enabled && !force_vacuum)
+ return;
- elog(DEBUG3, "%s: vac: %.0f (threshold %.0f), anl: %.0f (threshold %.0f)",
- NameStr(classForm->relname),
- vactuples, vacthresh, anltuples, anlthresh);
+ if (PointerIsValid(tabentry))
+ {
+ reltuples = classForm->reltuples;
+ vactuples = tabentry->n_dead_tuples;
+ anltuples = tabentry->n_live_tuples + tabentry->n_dead_tuples -
+ tabentry->last_anl_tuples;
- /* Determine if this table needs vacuum or analyze. */
- dovacuum = (vactuples > vacthresh);
- doanalyze = (anltuples > anlthresh);
+ vacthresh = (float4) vac_base_thresh + vac_scale_factor * reltuples;
+ anlthresh = (float4) anl_base_thresh + anl_scale_factor * reltuples;
+
+ /*
+ * Note that we don't need to take special consideration for stat
+ * reset, because if that happens, the last vacuum and analyze counts
+ * will be reset too.
+ */
+ elog(DEBUG3, "%s: vac: %.0f (threshold %.0f), anl: %.0f (threshold %.0f)",
+ NameStr(classForm->relname),
+ vactuples, vacthresh, anltuples, anlthresh);
+
+ /* Determine if this table needs vacuum or analyze. */
+ dovacuum = force_vacuum || (vactuples > vacthresh);
+ doanalyze = (anltuples > anlthresh);
+ }
+ else
+ {
+ /*
+ * Skip a table not found in stat hash, unless we have to force
+ * vacuum for anti-wrap purposes. If it's not acted upon, there's
+ * no need to vacuum it.
+ */
+ dovacuum = force_vacuum;
+ doanalyze = false;
+ }
/* ANALYZE refuses to work with pg_statistics */
if (relid == StatisticRelationId)
@@ -888,6 +845,7 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
tab->toastrelid = classForm->reltoastrelid;
tab->dovacuum = dovacuum;
tab->doanalyze = doanalyze;
+ tab->freeze_min_age = freeze_min_age;
tab->vacuum_cost_limit = vac_cost_limit;
tab->vacuum_cost_delay = vac_cost_delay;
@@ -904,11 +862,11 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
/*
* autovacuum_do_vac_analyze
- * Vacuum and/or analyze a list of tables; or all tables if relids = NIL
+ * Vacuum and/or analyze the specified table
*/
static void
-autovacuum_do_vac_analyze(List *relids, bool dovacuum, bool doanalyze,
- bool freeze)
+autovacuum_do_vac_analyze(Oid relid, bool dovacuum, bool doanalyze,
+ int freeze_min_age)
{
VacuumStmt *vacstmt;
MemoryContext old_cxt;
@@ -932,15 +890,15 @@ autovacuum_do_vac_analyze(List *relids, bool dovacuum, bool doanalyze,
vacstmt->vacuum = dovacuum;
vacstmt->full = false;
vacstmt->analyze = doanalyze;
- vacstmt->freeze = freeze;
+ vacstmt->freeze_min_age = freeze_min_age;
vacstmt->verbose = false;
- vacstmt->relation = NULL; /* all tables, or not used if relids != NIL */
+ vacstmt->relation = NULL; /* not used since we pass relids list */
vacstmt->va_cols = NIL;
/* Let pgstat know what we're doing */
- autovac_report_activity(vacstmt, relids);
+ autovac_report_activity(vacstmt, relid);
- vacuum(vacstmt, relids);
+ vacuum(vacstmt, list_make1_oid(relid));
pfree(vacstmt);
MemoryContextSwitchTo(old_cxt);
@@ -958,48 +916,35 @@ autovacuum_do_vac_analyze(List *relids, bool dovacuum, bool doanalyze,
* bother to report "<IDLE>" or some such.
*/
static void
-autovac_report_activity(VacuumStmt *vacstmt, List *relids)
+autovac_report_activity(VacuumStmt *vacstmt, Oid relid)
{
+ char *relname = get_rel_name(relid);
+ char *nspname = get_namespace_name(get_rel_namespace(relid));
#define MAX_AUTOVAC_ACTIV_LEN (NAMEDATALEN * 2 + 32)
char activity[MAX_AUTOVAC_ACTIV_LEN];
- /*
- * This case is not currently exercised by the autovac code. Fill it in
- * if needed.
- */
- if (list_length(relids) > 1)
- elog(WARNING, "vacuuming >1 rel unsupported");
-
/* Report the command and possible options */
if (vacstmt->vacuum)
snprintf(activity, MAX_AUTOVAC_ACTIV_LEN,
- "VACUUM%s%s%s",
- vacstmt->full ? " FULL" : "",
- vacstmt->freeze ? " FREEZE" : "",
+ "VACUUM%s",
vacstmt->analyze ? " ANALYZE" : "");
- else if (vacstmt->analyze)
+ else
snprintf(activity, MAX_AUTOVAC_ACTIV_LEN,
"ANALYZE");
- /* Report the qualified name of the first relation, if any */
- if (relids)
+ /*
+ * Report the qualified name of the relation.
+ *
+ * Paranoia is appropriate here in case relation was recently dropped
+ * --- the lsyscache routines we just invoked will return NULL rather
+ * than failing.
+ */
+ if (relname && nspname)
{
- Oid relid = linitial_oid(relids);
- char *relname = get_rel_name(relid);
- char *nspname = get_namespace_name(get_rel_namespace(relid));
-
- /*
- * Paranoia is appropriate here in case relation was recently dropped
- * --- the lsyscache routines we just invoked will return NULL rather
- * than failing.
- */
- if (relname && nspname)
- {
- int len = strlen(activity);
+ int len = strlen(activity);
- snprintf(activity + len, MAX_AUTOVAC_ACTIV_LEN - len,
- " %s.%s", nspname, relname);
- }
+ snprintf(activity + len, MAX_AUTOVAC_ACTIV_LEN - len,
+ " %s.%s", nspname, relname);
}
pgstat_report_activity(activity);
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index ed4de726980..caf7d9a82d4 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -37,7 +37,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.500 2006/10/04 00:29:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.501 2006/11/05 22:42:09 tgl Exp $
*
* NOTES
*
@@ -217,6 +217,8 @@ static bool FatalError = false; /* T if recovering from backend crash */
bool ClientAuthInProgress = false; /* T during new-client
* authentication */
+static bool force_autovac = false; /* received START_AUTOVAC signal */
+
/*
* State for assigning random salts and cancel keys.
* Also, the global MyCancelKey passes the cancel key assigned to a given
@@ -1231,9 +1233,13 @@ ServerLoop(void)
* (It'll die relatively quickly.) We check that it's not started too
* frequently in autovac_start.
*/
- if (AutoVacuumingActive() && AutoVacPID == 0 &&
+ if ((AutoVacuumingActive() || force_autovac) && AutoVacPID == 0 &&
StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
+ {
AutoVacPID = autovac_start();
+ if (AutoVacPID != 0)
+ force_autovac = false; /* signal successfully processed */
+ }
/* If we have lost the archiver, try to start a new one */
if (XLogArchivingActive() && PgArchPID == 0 &&
@@ -2100,9 +2106,7 @@ reaper(SIGNAL_ARGS)
/*
* Was it the autovacuum process? Normal exit can be ignored; we'll
* start a new one at the next iteration of the postmaster's main
- * loop, if necessary.
- *
- * An unexpected exit must crash the system.
+ * loop, if necessary. An unexpected exit is treated as a crash.
*/
if (AutoVacPID != 0 && pid == AutoVacPID)
{
@@ -3424,12 +3428,16 @@ sigusr1_handler(SIGNAL_ARGS)
if (CheckPostmasterSignal(PMSIGNAL_START_AUTOVAC))
{
- /* start one iteration of the autovacuum daemon */
- if (Shutdown == NoShutdown)
- {
- Assert(!AutoVacuumingActive());
- AutoVacPID = autovac_start();
- }
+ /*
+ * Start one iteration of the autovacuum daemon, even if autovacuuming
+ * is nominally not enabled. This is so we can have an active defense
+ * against transaction ID wraparound. We set a flag for the main loop
+ * to do it rather than trying to do it here --- this is because the
+ * autovac process itself may send the signal, and we want to handle
+ * that by launching another iteration as soon as the current one
+ * completes.
+ */
+ force_autovac = true;
}
PG_SETMASK(&UnBlockSig);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 39de167fe56..b037e594941 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -23,7 +23,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/ipc/procarray.c,v 1.18 2006/10/04 00:29:57 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/ipc/procarray.c,v 1.19 2006/11/05 22:42:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -213,7 +213,9 @@ TransactionIdIsInProgress(TransactionId xid)
/*
* Don't bother checking a transaction older than RecentXmin; it could not
- * possibly still be running.
+ * possibly still be running. (Note: in particular, this guarantees
+ * that we reject InvalidTransactionId, FrozenTransactionId, etc as
+ * not running.)
*/
if (TransactionIdPrecedes(xid, RecentXmin))
{
diff --git a/src/backend/utils/init/flatfiles.c b/src/backend/utils/init/flatfiles.c
index 8867ec3e015..dc117145fc0 100644
--- a/src/backend/utils/init/flatfiles.c
+++ b/src/backend/utils/init/flatfiles.c
@@ -23,7 +23,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.21 2006/07/14 14:52:25 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.22 2006/11/05 22:42:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -163,7 +163,7 @@ name_okay(const char *str)
/*
* write_database_file: update the flat database file
*
- * A side effect is to determine the oldest database's datminxid
+ * A side effect is to determine the oldest database's datfrozenxid
* so we can set or update the XID wrap limit.
*/
static void
@@ -177,7 +177,7 @@ write_database_file(Relation drel)
HeapScanDesc scan;
HeapTuple tuple;
NameData oldest_datname;
- TransactionId oldest_datminxid = InvalidTransactionId;
+ TransactionId oldest_datfrozenxid = InvalidTransactionId;
/*
* Create a temporary filename to be renamed later. This prevents the
@@ -208,27 +208,23 @@ write_database_file(Relation drel)
char *datname;
Oid datoid;
Oid dattablespace;
- TransactionId datminxid,
- datvacuumxid;
+ TransactionId datfrozenxid;
datname = NameStr(dbform->datname);
datoid = HeapTupleGetOid(tuple);
dattablespace = dbform->dattablespace;
- datminxid = dbform->datminxid;
- datvacuumxid = dbform->datvacuumxid;
+ datfrozenxid = dbform->datfrozenxid;
/*
- * Identify the oldest datminxid, ignoring databases that are not
- * connectable (we assume they are safely frozen). This must match
+ * Identify the oldest datfrozenxid. This must match
* the logic in vac_truncate_clog() in vacuum.c.
*/
- if (dbform->datallowconn &&
- TransactionIdIsNormal(datminxid))
+ if (TransactionIdIsNormal(datfrozenxid))
{
- if (oldest_datminxid == InvalidTransactionId ||
- TransactionIdPrecedes(datminxid, oldest_datminxid))
+ if (oldest_datfrozenxid == InvalidTransactionId ||
+ TransactionIdPrecedes(datfrozenxid, oldest_datfrozenxid))
{
- oldest_datminxid = datminxid;
+ oldest_datfrozenxid = datfrozenxid;
namestrcpy(&oldest_datname, datname);
}
}
@@ -244,14 +240,14 @@ write_database_file(Relation drel)
}
/*
- * The file format is: "dbname" oid tablespace minxid vacuumxid
+ * The file format is: "dbname" oid tablespace frozenxid
*
* The xids are not needed for backend startup, but are of use to
* autovacuum, and might also be helpful for forensic purposes.
*/
fputs_quote(datname, fp);
- fprintf(fp, " %u %u %u %u\n",
- datoid, dattablespace, datminxid, datvacuumxid);
+ fprintf(fp, " %u %u %u\n",
+ datoid, dattablespace, datfrozenxid);
}
heap_endscan(scan);
@@ -272,10 +268,10 @@ write_database_file(Relation drel)
tempname, filename)));
/*
- * Set the transaction ID wrap limit using the oldest datminxid
+ * Set the transaction ID wrap limit using the oldest datfrozenxid
*/
- if (oldest_datminxid != InvalidTransactionId)
- SetTransactionIdLimit(oldest_datminxid, &oldest_datname);
+ if (oldest_datfrozenxid != InvalidTransactionId)
+ SetTransactionIdLimit(oldest_datfrozenxid, &oldest_datname);
}
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 9ab8c9ba97f..82532a196b0 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.171 2006/10/04 00:30:02 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.172 2006/11/05 22:42:09 tgl Exp $
*
*
*-------------------------------------------------------------------------
@@ -77,7 +77,7 @@ FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace)
char *filename;
FILE *db_file;
char thisname[NAMEDATALEN];
- TransactionId dummyxid;
+ TransactionId db_frozenxid;
filename = database_getflatfilename();
db_file = AllocateFile(filename, "r");
@@ -87,8 +87,7 @@ FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace)
errmsg("could not open file \"%s\": %m", filename)));
while (read_pg_database_line(db_file, thisname, db_id,
- db_tablespace, &dummyxid,
- &dummyxid))
+ db_tablespace, &db_frozenxid))
{
if (strcmp(thisname, name) == 0)
{
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index f0ff66fa66a..5bbf89da5bd 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10,7 +10,7 @@
* Written by Peter Eisentraut <peter_e@gmx.net>.
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.357 2006/10/19 18:32:47 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.358 2006/11/05 22:42:09 tgl Exp $
*
*--------------------------------------------------------------------
*/
@@ -1331,6 +1331,15 @@ static struct config_int ConfigureNamesInt[] =
},
{
+ {"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Minimum age at which VACUUM should freeze a table row."),
+ NULL
+ },
+ &vacuum_freeze_min_age,
+ 100000000, 0, 1000000000, NULL, NULL
+ },
+
+ {
{"max_fsm_relations", PGC_POSTMASTER, RESOURCES_FSM,
gettext_noop("Sets the maximum number of tables and indexes for which free space is tracked."),
NULL
@@ -1576,6 +1585,15 @@ static struct config_int ConfigureNamesInt[] =
&autovacuum_anl_thresh,
250, 0, INT_MAX, NULL, NULL
},
+ {
+ /* see varsup.c for why this is PGC_POSTMASTER not PGC_SIGHUP */
+ {"autovacuum_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM,
+ gettext_noop("Age at which to autovacuum a table to prevent transacion ID wraparound."),
+ NULL
+ },
+ &autovacuum_freeze_max_age,
+ 200000000, 100000000, 2000000000, NULL, NULL
+ },
{
{"tcp_keepalives_idle", PGC_USERSET, CLIENT_CONN_OTHER,
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 1d0e842bba0..3a0dedba672 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -373,6 +373,8 @@
# vacuum
#autovacuum_analyze_scale_factor = 0.1 # fraction of rel size before
# analyze
+#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum
+ # (change requires restart)
#autovacuum_vacuum_cost_delay = -1 # default vacuum cost delay for
# autovacuum, -1 means use
# vacuum_cost_delay
@@ -394,6 +396,7 @@
#default_transaction_isolation = 'read committed'
#default_transaction_read_only = off
#statement_timeout = 0 # 0 is disabled
+#vacuum_freeze_min_age = 100000000
# - Locale and Formatting -
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index eedc6222be8..2bdb6d9e714 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -32,7 +32,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/time/tqual.c,v 1.98 2006/10/04 00:30:04 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/time/tqual.c,v 1.99 2006/11/05 22:42:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1099,9 +1099,11 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin,
{
/*
* "Deleting" xact really only locked it, so the tuple is live in any
- * case. However, we must make sure that either XMAX_COMMITTED or
- * XMAX_INVALID gets set once the xact is gone; otherwise it is unsafe
- * to recycle CLOG status after vacuuming.
+ * case. However, we should make sure that either XMAX_COMMITTED or
+ * XMAX_INVALID gets set once the xact is gone, to reduce the costs
+ * of examining the tuple for future xacts. Also, marking dead
+ * MultiXacts as invalid here provides defense against MultiXactId
+ * wraparound (see also comments in heap_freeze_tuple()).
*/
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
diff --git a/src/include/access/clog.h b/src/include/access/clog.h
index 999f15bdf7f..0cb76000a29 100644
--- a/src/include/access/clog.h
+++ b/src/include/access/clog.h
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/access/clog.h,v 1.17 2006/03/24 04:32:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/access/clog.h,v 1.18 2006/11/05 22:42:09 tgl Exp $
*/
#ifndef CLOG_H
#define CLOG_H
@@ -46,6 +46,7 @@ extern void TruncateCLOG(TransactionId oldestXact);
/* XLOG stuff */
#define CLOG_ZEROPAGE 0x00
+#define CLOG_TRUNCATE 0x10
extern void clog_redo(XLogRecPtr lsn, XLogRecord *record);
extern void clog_desc(StringInfo buf, uint8 xl_info, char *rec);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index b2dd0f3390d..4b3dd57534c 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.116 2006/10/04 00:30:07 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.117 2006/11/05 22:42:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -170,6 +170,8 @@ extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
TransactionId *update_xmax, CommandId cid,
LockTupleMode mode, bool nowait);
extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
+ Buffer buf);
extern Oid simple_heap_insert(Relation relation, HeapTuple tup);
extern void simple_heap_delete(Relation relation, ItemPointer tid);
@@ -181,11 +183,17 @@ extern void heap_restrpos(HeapScanDesc scan);
extern void heap_redo(XLogRecPtr lsn, XLogRecord *rptr);
extern void heap_desc(StringInfo buf, uint8 xl_info, char *rec);
-extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer,
- OffsetNumber *unused, int uncnt);
+extern void heap2_redo(XLogRecPtr lsn, XLogRecord *rptr);
+extern void heap2_desc(StringInfo buf, uint8 xl_info, char *rec);
+
extern XLogRecPtr log_heap_move(Relation reln, Buffer oldbuf,
ItemPointerData from,
Buffer newbuf, HeapTuple newtup);
+extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer,
+ OffsetNumber *unused, int uncnt);
+extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
+ TransactionId cutoff_xid,
+ OffsetNumber *offsets, int offcnt);
/* in common/heaptuple.c */
extern Size heap_compute_data_size(TupleDesc tupleDesc,
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index edfce82bc04..ed1f082de05 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/access/htup.h,v 1.86 2006/10/04 00:30:07 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/htup.h,v 1.87 2006/11/05 22:42:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -510,6 +510,13 @@ typedef HeapTupleData *HeapTuple;
* we can (and we do) restore entire page in redo
*/
#define XLOG_HEAP_INIT_PAGE 0x80
+/*
+ * We ran out of opcodes, so heapam.c now has a second RmgrId. These opcodes
+ * are associated with RM_HEAP2_ID, but are not logically different from
+ * the ones above associated with RM_HEAP_ID. We apply XLOG_HEAP_OPMASK,
+ * although currently XLOG_HEAP_INIT_PAGE is not used for any of these.
+ */
+#define XLOG_HEAP2_FREEZE 0x00
/*
* All what we need to find changed tuple
@@ -613,4 +620,15 @@ typedef struct xl_heap_inplace
#define SizeOfHeapInplace (offsetof(xl_heap_inplace, target) + SizeOfHeapTid)
+/* This is what we need to know about tuple freezing during vacuum */
+typedef struct xl_heap_freeze
+{
+ RelFileNode node;
+ BlockNumber block;
+ TransactionId cutoff_xid;
+ /* TUPLE OFFSET NUMBERS FOLLOW AT THE END */
+} xl_heap_freeze;
+
+#define SizeOfHeapFreeze (offsetof(xl_heap_freeze, cutoff_xid) + sizeof(TransactionId))
+
#endif /* HTUP_H */
diff --git a/src/include/access/rmgr.h b/src/include/access/rmgr.h
index 471b0cfb764..7be2dfc9f65 100644
--- a/src/include/access/rmgr.h
+++ b/src/include/access/rmgr.h
@@ -3,7 +3,7 @@
*
* Resource managers definition
*
- * $PostgreSQL: pgsql/src/include/access/rmgr.h,v 1.16 2006/05/02 11:28:55 teodor Exp $
+ * $PostgreSQL: pgsql/src/include/access/rmgr.h,v 1.17 2006/11/05 22:42:10 tgl Exp $
*/
#ifndef RMGR_H
#define RMGR_H
@@ -12,6 +12,9 @@ typedef uint8 RmgrId;
/*
* Built-in resource managers
+ *
+ * Note: RM_MAX_ID could be as much as 255 without breaking the XLOG file
+ * format, but we keep it small to minimize the size of RmgrTable[].
*/
#define RM_XLOG_ID 0
#define RM_XACT_ID 1
@@ -20,6 +23,7 @@ typedef uint8 RmgrId;
#define RM_DBASE_ID 4
#define RM_TBLSPC_ID 5
#define RM_MULTIXACT_ID 6
+#define RM_HEAP2_ID 9
#define RM_HEAP_ID 10
#define RM_BTREE_ID 11
#define RM_HASH_ID 12
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index f1b91145f67..96cc65f94d5 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/access/transam.h,v 1.58 2006/07/10 16:20:51 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/access/transam.h,v 1.59 2006/11/05 22:42:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -23,7 +23,7 @@
* always be considered valid.
*
* FirstNormalTransactionId is the first "normal" transaction id.
- * Note: if you need to change it, you must change it in pg_class.h as well.
+ * Note: if you need to change it, you must change pg_class.h as well.
* ----------------
*/
#define InvalidTransactionId ((TransactionId) 0)
@@ -88,6 +88,9 @@ typedef struct VariableCacheData
Oid nextOid; /* next OID to assign */
uint32 oidCount; /* OIDs available before must do XLOG work */
TransactionId nextXid; /* next XID to assign */
+
+ TransactionId oldestXid; /* cluster-wide minimum datfrozenxid */
+ TransactionId xidVacLimit; /* start forcing autovacuums here */
TransactionId xidWarnLimit; /* start complaining here */
TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */
TransactionId xidWrapLimit; /* where the world ends */
@@ -124,7 +127,7 @@ extern bool TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2);
/* in transam/varsup.c */
extern TransactionId GetNewTransactionId(bool isSubXact);
extern TransactionId ReadNewTransactionId(void);
-extern void SetTransactionIdLimit(TransactionId oldest_datminxid,
+extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
Name oldest_datname);
extern Oid GetNewObjectId(void);
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index a5ae94b91aa..93c95aa4627 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/access/xlog.h,v 1.74 2006/08/21 16:16:31 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/access/xlog.h,v 1.75 2006/11/05 22:42:10 tgl Exp $
*/
#ifndef XLOG_H
#define XLOG_H
@@ -165,7 +165,6 @@ extern void InitXLOGAccess(void);
extern void CreateCheckPoint(bool shutdown, bool force);
extern void XLogPutNextOid(Oid nextOid);
extern XLogRecPtr GetRedoRecPtr(void);
-extern TransactionId GetRecentNextXid(void);
extern void GetNextXidAndEpoch(TransactionId *xid, uint32 *epoch);
#endif /* XLOG_H */
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index f5da840d2d0..2f61b946758 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.358 2006/09/18 22:40:38 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.359 2006/11/05 22:42:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200609181
+#define CATALOG_VERSION_NO 200611051
#endif
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index b96a5b83404..3fcc5ed6746 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.125 2006/10/04 00:30:07 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.126 2006/11/05 22:42:10 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
@@ -404,10 +404,9 @@ DATA(insert ( 1249 tableoid 26 0 4 -7 0 -1 -1 t p i t f f t 0));
{ 1259, {"relhaspkey"}, 16, -1, 1, 22, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
{ 1259, {"relhasrules"}, 16, -1, 1, 23, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
{ 1259, {"relhassubclass"},16, -1, 1, 24, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1259, {"relminxid"}, 28, -1, 4, 25, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"relvacuumxid"}, 28, -1, 4, 26, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"relacl"}, 1034, -1, -1, 27, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
-{ 1259, {"reloptions"}, 1009, -1, -1, 28, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }
+{ 1259, {"relfrozenxid"}, 28, -1, 4, 25, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
+{ 1259, {"relacl"}, 1034, -1, -1, 26, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
+{ 1259, {"reloptions"}, 1009, -1, -1, 27, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }
DATA(insert ( 1259 relname 19 -1 NAMEDATALEN 1 0 -1 -1 f p i t f f t 0));
DATA(insert ( 1259 relnamespace 26 -1 4 2 0 -1 -1 t p i t f f t 0));
@@ -433,10 +432,9 @@ DATA(insert ( 1259 relhasoids 16 -1 1 21 0 -1 -1 t p c t f f t 0));
DATA(insert ( 1259 relhaspkey 16 -1 1 22 0 -1 -1 t p c t f f t 0));
DATA(insert ( 1259 relhasrules 16 -1 1 23 0 -1 -1 t p c t f f t 0));
DATA(insert ( 1259 relhassubclass 16 -1 1 24 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1259 relminxid 28 -1 4 25 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 relvacuumxid 28 -1 4 26 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 relacl 1034 -1 -1 27 1 -1 -1 f x i f f f t 0));
-DATA(insert ( 1259 reloptions 1009 -1 -1 28 1 -1 -1 f x i f f f t 0));
+DATA(insert ( 1259 relfrozenxid 28 -1 4 25 0 -1 -1 t p i t f f t 0));
+DATA(insert ( 1259 relacl 1034 -1 -1 26 1 -1 -1 f x i f f f t 0));
+DATA(insert ( 1259 reloptions 1009 -1 -1 27 1 -1 -1 f x i f f f t 0));
DATA(insert ( 1259 ctid 27 0 6 -1 0 -1 -1 f p s t f f t 0));
DATA(insert ( 1259 oid 26 0 4 -2 0 -1 -1 t p i t f f t 0));
DATA(insert ( 1259 xmin 28 0 4 -3 0 -1 -1 t p i t f f t 0));
diff --git a/src/include/catalog/pg_autovacuum.h b/src/include/catalog/pg_autovacuum.h
index 5e3db86a43b..e46da9c649d 100644
--- a/src/include/catalog/pg_autovacuum.h
+++ b/src/include/catalog/pg_autovacuum.h
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_autovacuum.h,v 1.4 2006/03/05 15:58:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_autovacuum.h,v 1.5 2006/11/05 22:42:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -28,14 +28,16 @@
#define AutovacuumRelationId 1248
CATALOG(pg_autovacuum,1248) BKI_WITHOUT_OIDS
{
- Oid vacrelid; /* OID of table */
- bool enabled; /* enabled for this table? */
+ Oid vacrelid; /* OID of table */
+ bool enabled; /* enabled for this table? */
int4 vac_base_thresh; /* base threshold value */
- float4 vac_scale_factor; /* reltuples scaling factor */
+ float4 vac_scale_factor; /* reltuples scaling factor */
int4 anl_base_thresh; /* base threshold value */
- float4 anl_scale_factor; /* reltuples scaling factor */
- int4 vac_cost_delay; /* vacuum cost-based delay */
- int4 vac_cost_limit; /* vacuum cost limit */
+ float4 anl_scale_factor; /* reltuples scaling factor */
+ int4 vac_cost_delay; /* vacuum cost-based delay */
+ int4 vac_cost_limit; /* vacuum cost limit */
+ int4 freeze_min_age; /* vacuum min freeze age */
+ int4 freeze_max_age; /* max age before forcing vacuum */
} FormData_pg_autovacuum;
/* ----------------
@@ -49,7 +51,7 @@ typedef FormData_pg_autovacuum *Form_pg_autovacuum;
* compiler constants for pg_autovacuum
* ----------------
*/
-#define Natts_pg_autovacuum 8
+#define Natts_pg_autovacuum 10
#define Anum_pg_autovacuum_vacrelid 1
#define Anum_pg_autovacuum_enabled 2
#define Anum_pg_autovacuum_vac_base_thresh 3
@@ -58,6 +60,8 @@ typedef FormData_pg_autovacuum *Form_pg_autovacuum;
#define Anum_pg_autovacuum_anl_scale_factor 6
#define Anum_pg_autovacuum_vac_cost_delay 7
#define Anum_pg_autovacuum_vac_cost_limit 8
+#define Anum_pg_autovacuum_freeze_min_age 9
+#define Anum_pg_autovacuum_freeze_max_age 10
/* There are no preloaded tuples in pg_autovacuum.h */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index aa0c02ca1d9..75aee92e512 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_class.h,v 1.96 2006/10/04 00:30:07 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_class.h,v 1.97 2006/11/05 22:42:10 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
@@ -65,8 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP
bool relhaspkey; /* has PRIMARY KEY index */
bool relhasrules; /* has associated rules */
bool relhassubclass; /* has derived classes */
- TransactionId relminxid; /* minimum Xid present in table */
- TransactionId relvacuumxid; /* Xid used as last vacuum OldestXmin */
+ TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
/*
* VARIABLE LENGTH FIELDS start here. These fields may be NULL, too.
@@ -80,7 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP
/* Size of fixed part of pg_class tuples, not counting var-length fields */
#define CLASS_TUPLE_SIZE \
- (offsetof(FormData_pg_class,relvacuumxid) + sizeof(TransactionId))
+ (offsetof(FormData_pg_class,relfrozenxid) + sizeof(TransactionId))
/* ----------------
* Form_pg_class corresponds to a pointer to a tuple with
@@ -94,7 +93,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 28
+#define Natts_pg_class 27
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -119,27 +118,27 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relhaspkey 22
#define Anum_pg_class_relhasrules 23
#define Anum_pg_class_relhassubclass 24
-#define Anum_pg_class_relminxid 25
-#define Anum_pg_class_relvacuumxid 26
-#define Anum_pg_class_relacl 27
-#define Anum_pg_class_reloptions 28
+#define Anum_pg_class_relfrozenxid 25
+#define Anum_pg_class_relacl 26
+#define Anum_pg_class_reloptions 27
/* ----------------
* initial contents of pg_class
*
* NOTE: only "bootstrapped" relations need to be declared here. Be sure that
- * the OIDs listed here match those given in their CATALOG macros.
+ * the OIDs listed here match those given in their CATALOG macros, and that
+ * the relnatts values are correct.
* ----------------
*/
-/* Note: the "3" here stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f r 23 0 0 0 0 0 t f f f 3 3 _null_ _null_ ));
+/* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
+DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f r 23 0 0 0 0 0 t f f f 3 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f r 17 0 0 0 0 0 f f f f 3 3 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f r 17 0 0 0 0 0 f f f f 3 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f r 18 0 0 0 0 0 t f f f 3 3 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f r 18 0 0 0 0 0 t f f f 3 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f r 28 0 0 0 0 0 t f f f 3 3 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f r 27 0 0 0 0 0 t f f f 3 _null_ _null_ ));
DESCR("");
#define RELKIND_INDEX 'i' /* secondary index */
diff --git a/src/include/catalog/pg_database.h b/src/include/catalog/pg_database.h
index d62a4b94928..c3d80bebcb7 100644
--- a/src/include/catalog/pg_database.h
+++ b/src/include/catalog/pg_database.h
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_database.h,v 1.41 2006/07/10 16:20:51 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_database.h,v 1.42 2006/11/05 22:42:10 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
@@ -42,8 +42,7 @@ CATALOG(pg_database,1262) BKI_SHARED_RELATION
bool datallowconn; /* new connections allowed? */
int4 datconnlimit; /* max connections allowed (-1=no limit) */
Oid datlastsysoid; /* highest OID to consider a system OID */
- TransactionId datvacuumxid; /* all XIDs before this are vacuumed */
- TransactionId datminxid; /* minimum XID present anywhere in the DB */
+ TransactionId datfrozenxid; /* all Xids < this are frozen in this DB */
Oid dattablespace; /* default table space for this DB */
text datconfig[1]; /* database-specific GUC (VAR LENGTH) */
aclitem datacl[1]; /* access permissions (VAR LENGTH) */
@@ -60,7 +59,7 @@ typedef FormData_pg_database *Form_pg_database;
* compiler constants for pg_database
* ----------------
*/
-#define Natts_pg_database 12
+#define Natts_pg_database 11
#define Anum_pg_database_datname 1
#define Anum_pg_database_datdba 2
#define Anum_pg_database_encoding 3
@@ -68,13 +67,12 @@ typedef FormData_pg_database *Form_pg_database;
#define Anum_pg_database_datallowconn 5
#define Anum_pg_database_datconnlimit 6
#define Anum_pg_database_datlastsysoid 7
-#define Anum_pg_database_datvacuumxid 8
-#define Anum_pg_database_datminxid 9
-#define Anum_pg_database_dattablespace 10
-#define Anum_pg_database_datconfig 11
-#define Anum_pg_database_datacl 12
+#define Anum_pg_database_datfrozenxid 8
+#define Anum_pg_database_dattablespace 9
+#define Anum_pg_database_datconfig 10
+#define Anum_pg_database_datacl 11
-DATA(insert OID = 1 ( template1 PGUID ENCODING t t -1 0 0 0 1663 _null_ _null_ ));
+DATA(insert OID = 1 ( template1 PGUID ENCODING t t -1 0 0 1663 _null_ _null_ ));
SHDESCR("Default template database");
#define TemplateDbOid 1
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index fed481971d2..5808a581cc5 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.67 2006/07/13 18:01:02 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.68 2006/11/05 22:42:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -104,8 +104,9 @@ typedef struct VacAttrStats
} VacAttrStats;
-/* Default statistics target (GUC parameter) */
+/* GUC parameters */
extern DLLIMPORT int default_statistics_target; /* DLLIMPORT for PostGIS */
+extern int vacuum_freeze_min_age;
/* in commands/vacuum.c */
@@ -117,14 +118,13 @@ extern void vac_update_relstats(Oid relid,
BlockNumber num_pages,
double num_tuples,
bool hasindex,
- TransactionId minxid,
- TransactionId vacuumxid);
+ TransactionId frozenxid);
extern void vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel,
TransactionId *oldestXmin,
TransactionId *freezeLimit);
+extern void vac_update_datfrozenxid(void);
extern bool vac_is_partial_index(Relation indrel);
extern void vacuum_delay_point(void);
-extern TransactionId vactuple_get_minxid(HeapTuple tuple);
/* in commands/vacuumlazy.c */
extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt);
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index eb8539f1399..155db7314d5 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -4,7 +4,7 @@
* Interface to hba.c
*
*
- * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.44 2006/10/04 00:30:08 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.45 2006/11/05 22:42:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -40,7 +40,6 @@ extern void load_role(void);
extern int hba_getauthmethod(hbaPort *port);
extern int authident(hbaPort *port);
extern bool read_pg_database_line(FILE *fp, char *dbname, Oid *dboid,
- Oid *dbtablespace, TransactionId *dbminxid,
- TransactionId *dbvacuumxid);
+ Oid *dbtablespace, TransactionId *dbfrozenxid);
#endif /* HBA_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4f7351236f6..f79bf2907ce 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.333 2006/10/13 21:43:19 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.334 2006/11/05 22:42:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1756,8 +1756,8 @@ typedef struct VacuumStmt
bool vacuum; /* do VACUUM step */
bool full; /* do FULL (non-concurrent) vacuum */
bool analyze; /* do ANALYZE step */
- bool freeze; /* early-freeze option */
bool verbose; /* print progress info */
+ int freeze_min_age; /* min freeze age, or -1 to use default */
RangeVar *relation; /* single table to process, or NULL */
List *va_cols; /* list of column names, or NIL for all */
} VacuumStmt;
diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h
index 3de77f2ed17..b36334fce8e 100644
--- a/src/include/postmaster/autovacuum.h
+++ b/src/include/postmaster/autovacuum.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/postmaster/autovacuum.h,v 1.4 2006/03/05 15:58:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/postmaster/autovacuum.h,v 1.5 2006/11/05 22:42:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -21,6 +21,7 @@ extern int autovacuum_vac_thresh;
extern double autovacuum_vac_scale;
extern int autovacuum_anl_thresh;
extern double autovacuum_anl_scale;
+extern int autovacuum_freeze_max_age;
extern int autovacuum_vac_cost_delay;
extern int autovacuum_vac_cost_limit;