diff options
Diffstat (limited to 'src')
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; |