From 42395400b13ace519d0058122407604f1c23ad07 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Fri, 3 May 2019 15:15:20 -0400 Subject: [PATCH] vacuum: Don't try to truncate if the XID warn limit has been reached. --- src/backend/access/heap/vacuumlazy.c | 58 ++++++++++++++++++---------- src/backend/access/transam/varsup.c | 13 +++++++ src/backend/commands/cluster.c | 2 +- src/backend/commands/vacuum.c | 18 +++++++-- src/include/access/transam.h | 2 + src/include/commands/vacuum.h | 3 +- 6 files changed, 70 insertions(+), 26 deletions(-) diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 2aa729817c..4b963c0ed8 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -136,6 +136,7 @@ typedef struct LVRelStats int num_index_scans; TransactionId latestRemovedXid; bool lock_waiter_detected; + bool warn_limit_reached; } LVRelStats; @@ -207,6 +208,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params, double new_live_tuples; TransactionId new_frozen_xid; MultiXactId new_min_multi; + bool warn_limit_reached; Assert(params != NULL); Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT); @@ -238,7 +240,8 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params, params->multixact_freeze_min_age, params->multixact_freeze_table_age, &OldestXmin, &FreezeLimit, &xidFullScanLimit, - &MultiXactCutoff, &mxactFullScanLimit); + &MultiXactCutoff, &mxactFullScanLimit, + &warn_limit_reached); /* * We request an aggressive scan if the table's frozen Xid is now older @@ -277,6 +280,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params, vacrelstats->num_index_scans = 0; vacrelstats->pages_removed = 0; vacrelstats->lock_waiter_detected = false; + vacrelstats->warn_limit_reached = warn_limit_reached; /* Open all indexes of the relation */ vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel); @@ -1065,9 +1069,9 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, * cheaper to get rid of it in the next pruning pass than * to treat it like an indexed tuple. Finally, if index * cleanup is disabled, the second heap pass will not - * execute, and the tuple will not get removed, so we - * must treat it like any other dead tuple that we choose - * to keep. + * execute, and the tuple will not get removed, so we must + * treat it like any other dead tuple that we choose to + * keep. * * If this were to happen for a tuple that actually needed * to be deleted, we'd be in trouble, because it'd @@ -1085,6 +1089,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, all_visible = false; break; case HEAPTUPLE_LIVE: + /* * Count it as live. Not only is this natural, but it's * also what acquire_sample_rows() does. @@ -1249,13 +1254,14 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, else { /* - * Here, we have indexes but index cleanup is disabled. Instead of - * vacuuming the dead tuples on the heap, we just forget them. + * Here, we have indexes but index cleanup is disabled. + * Instead of vacuuming the dead tuples on the heap, we just + * forget them. * * Note that vacrelstats->dead_tuples could have tuples which * became dead after HOT-pruning but are not marked dead yet. - * We do not process them because it's a very rare condition, and - * the next vacuum will process them anyway. + * We do not process them because it's a very rare condition, + * and the next vacuum will process them anyway. */ Assert(params->index_cleanup == VACOPT_TERNARY_DISABLED); } @@ -1829,18 +1835,6 @@ lazy_cleanup_index(Relation indrel, /* * should_attempt_truncation - should we attempt to truncate the heap? * - * Don't even think about it unless we have a shot at releasing a goodly - * number of pages. Otherwise, the time taken isn't worth it. - * - * Also don't attempt it if we are doing early pruning/vacuuming, because a - * scan which cannot find a truncated heap page cannot determine that the - * snapshot is too old to read that page. We might be able to get away with - * truncating all except one of the pages, setting its LSN to (at least) the - * maximum of the truncated range if we also treated an index leaf tuple - * pointing to a missing heap page as something to trigger the "snapshot too - * old" error, but that seems fragile and seems like it deserves its own patch - * if we consider it. - * * This is split out so that we can test whether truncation is going to be * called for before we actually do it. If you change the logic here, be * careful to depend only on fields that lazy_scan_heap updates on-the-fly. @@ -1850,10 +1844,34 @@ should_attempt_truncation(Relation rel, LVRelStats *vacrelstats) { BlockNumber possibly_freeable; + /* Don't truncate if truncation is disabled for this relation. */ if (rel->rd_options != NULL && ((StdRdOptions *) rel->rd_options)->vacuum_truncate == false) return false; + /* + * Also don't truncate if we are very close to transaction ID wraparound. + * Truncating the relation will require an AccessExclusiveLock, which will + * force an XID assignment. If there are not very many XIDs left before + * the system starts refusing new write transactions, it is better to skip + * truncation. + */ + if (vacrelstats->warn_limit_reached) + return false; + + /* + * Finally, don't try to truncate unless we have a shot at releasing a + * goodly number of pages. Otherwise, the time taken isn't worth it. + * + * Also don't attempt it if we are doing early pruning/vacuuming, because + * a scan which cannot find a truncated heap page cannot determine that + * the snapshot is too old to read that page. We might be able to get + * away with truncating all except one of the pages, setting its LSN to + * (at least) the maximum of the truncated range if we also treated an + * index leaf tuple pointing to a missing heap page as something to + * trigger the "snapshot too old" error, but that seems fragile and seems + * like it deserves its own patch if we consider it. + */ possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages; if (possibly_freeable > 0 && (possibly_freeable >= REL_TRUNCATE_MINIMUM || diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index 788b961ef0..e19bbb68f1 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -254,6 +254,19 @@ ReadNextFullTransactionId(void) return fullXid; } +/* + * Read the next XID that will be allocated without actually allocating it, + * and also the xidWarnLimit. + */ +void +ReadNextXIDAndWarnLimit(TransactionId *nextXid, TransactionId *xidWarnLimit) +{ + LWLockAcquire(XidGenLock, LW_SHARED); + *nextXid = XidFromFullTransactionId(ShmemVariableCache->nextFullXid); + *xidWarnLimit = ShmemVariableCache->xidWarnLimit; + LWLockRelease(XidGenLock); +} + /* * Advance nextFullXid to the value after a given xid. The epoch is inferred. * This must only be called during recovery or from two-phase start-up code. diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 3ee7056047..3e166a07c7 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -864,7 +864,7 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, */ vacuum_set_xid_limits(OldHeap, 0, 0, 0, 0, &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff, - NULL); + NULL, NULL); /* * FreezeXid will become the table's new relfrozenxid, and that mustn't go diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 94fb6f2606..a9ddfdc5fa 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -859,9 +859,11 @@ get_all_vacuum_rels(int options) * Xmax. * - mxactFullScanLimit is a value against which a table's relminmxid value is * compared to produce a full-table vacuum, as with xidFullScanLimit. + * - warn_limit_reached is set to true if the cluster has reached the point + * where we emit warnings about imminent wraparound, and false otherwise. * - * xidFullScanLimit and mxactFullScanLimit can be passed as NULL if caller is - * not interested. + * xidFullScanLimit, mxactFullScanLimit, and warn_limit_reached can be passed + * as NULL if caller is not interested. */ void vacuum_set_xid_limits(Relation rel, @@ -873,11 +875,14 @@ vacuum_set_xid_limits(Relation rel, TransactionId *freezeLimit, TransactionId *xidFullScanLimit, MultiXactId *multiXactCutoff, - MultiXactId *mxactFullScanLimit) + MultiXactId *mxactFullScanLimit, + bool *warn_limit_reached) { int freezemin; int mxid_freezemin; int effective_multixact_freeze_max_age; + TransactionId nextXid; + TransactionId xidWarnLimit; TransactionId limit; TransactionId safeLimit; MultiXactId mxactLimit; @@ -921,7 +926,8 @@ vacuum_set_xid_limits(Relation rel, * autovacuum_freeze_max_age / 2 XIDs old), complain and force a minimum * freeze age of zero. */ - safeLimit = ReadNewTransactionId() - autovacuum_freeze_max_age; + ReadNextXIDAndWarnLimit(&nextXid, &xidWarnLimit); + safeLimit = nextXid - autovacuum_freeze_max_age; if (!TransactionIdIsNormal(safeLimit)) safeLimit = FirstNormalTransactionId; @@ -934,6 +940,10 @@ vacuum_set_xid_limits(Relation rel, limit = *oldestXmin; } + if (warn_limit_reached) + *warn_limit_reached = + TransactionIdFollowsOrEquals(nextXid, xidWarnLimit); + *freezeLimit = limit; /* diff --git a/src/include/access/transam.h b/src/include/access/transam.h index 7966a9e90b..1c789f55d7 100644 --- a/src/include/access/transam.h +++ b/src/include/access/transam.h @@ -225,6 +225,8 @@ extern XLogRecPtr TransactionIdGetCommitLSN(TransactionId xid); extern FullTransactionId GetNewTransactionId(bool isSubXact); extern void AdvanceNextFullTransactionIdPastXid(TransactionId xid); extern FullTransactionId ReadNextFullTransactionId(void); +extern void ReadNextXIDAndWarnLimit(TransactionId *nextXid, + TransactionId *xidWarnLimit); extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid); extern void AdvanceOldestClogXid(TransactionId oldest_datfrozenxid); diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 9cc6e0d023..cf6da0f785 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -219,7 +219,8 @@ extern void vacuum_set_xid_limits(Relation rel, TransactionId *freezeLimit, TransactionId *xidFullScanLimit, MultiXactId *multiXactCutoff, - MultiXactId *mxactFullScanLimit); + MultiXactId *mxactFullScanLimit, + bool *warn_limit_reached); extern void vac_update_datfrozenxid(void); extern void vacuum_delay_point(void); extern bool vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple, -- 2.17.2 (Apple Git-113)