diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/commands/vacuumlazy.c | 209 |
1 files changed, 135 insertions, 74 deletions
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 61d2edd262..fe87243f4c 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -106,6 +106,7 @@ typedef struct LVRelStats BlockNumber rel_pages; /* total number of pages */ BlockNumber scanned_pages; /* number of pages we examined */ BlockNumber pinskipped_pages; /* # of pages we skipped due to a pin */ + BlockNumber frozenskipped_pages; /* # of frozen pages we skipped */ double scanned_tuples; /* counts only tuples on scanned pages */ double old_rel_tuples; /* previous value of pg_class.reltuples */ double new_rel_tuples; /* new estimated total # of tuples */ @@ -136,7 +137,7 @@ static BufferAccessStrategy vac_strategy; /* non-export function prototypes */ static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, - Relation *Irel, int nindexes, bool scan_all); + Relation *Irel, int nindexes, bool aggressive); static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats); static bool lazy_check_needs_freeze(Buffer buf, bool *hastup); static void lazy_vacuum_index(Relation indrel, @@ -182,8 +183,8 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params, int usecs; double read_rate, write_rate; - bool scan_all; /* should we scan all pages? */ - bool scanned_all; /* did we actually scan all pages? */ + bool aggressive; /* should we scan all unfrozen pages? */ + bool scanned_all_unfrozen; /* actually scanned all such pages? */ TransactionId xidFullScanLimit; MultiXactId mxactFullScanLimit; BlockNumber new_rel_pages; @@ -221,15 +222,15 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params, &MultiXactCutoff, &mxactFullScanLimit); /* - * We request a full scan if either the table's frozen Xid is now older - * than or equal to the requested Xid full-table scan limit; or if the - * table's minimum MultiXactId is older than or equal to the requested + * We request an aggressive scan if either the table's frozen Xid is now + * older than or equal to the requested Xid full-table scan limit; or if + * the table's minimum MultiXactId is older than or equal to the requested * mxid full-table scan limit. */ - scan_all = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid, - xidFullScanLimit); - scan_all |= MultiXactIdPrecedesOrEquals(onerel->rd_rel->relminmxid, - mxactFullScanLimit); + aggressive = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid, + xidFullScanLimit); + aggressive |= MultiXactIdPrecedesOrEquals(onerel->rd_rel->relminmxid, + mxactFullScanLimit); vacrelstats = (LVRelStats *) palloc0(sizeof(LVRelStats)); @@ -244,7 +245,7 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params, vacrelstats->hasindex = (nindexes > 0); /* Do the vacuuming */ - lazy_scan_heap(onerel, vacrelstats, Irel, nindexes, scan_all); + lazy_scan_heap(onerel, vacrelstats, Irel, nindexes, aggressive); /* Done with indexes */ vac_close_indexes(nindexes, Irel, NoLock); @@ -256,13 +257,14 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params, * NB: We need to check this before truncating the relation, because that * will change ->rel_pages. */ - if (vacrelstats->scanned_pages < vacrelstats->rel_pages) + if ((vacrelstats->scanned_pages + vacrelstats->frozenskipped_pages) + < vacrelstats->rel_pages) { - Assert(!scan_all); - scanned_all = false; + Assert(!aggressive); + scanned_all_unfrozen = false; } else - scanned_all = true; + scanned_all_unfrozen = true; /* * Optionally truncate the relation. @@ -302,8 +304,8 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params, if (new_rel_allvisible > new_rel_pages) new_rel_allvisible = new_rel_pages; - new_frozen_xid = scanned_all ? FreezeLimit : InvalidTransactionId; - new_min_multi = scanned_all ? MultiXactCutoff : InvalidMultiXactId; + new_frozen_xid = scanned_all_unfrozen ? FreezeLimit : InvalidTransactionId; + new_min_multi = scanned_all_unfrozen ? MultiXactCutoff : InvalidMultiXactId; vac_update_relstats(onerel, new_rel_pages, @@ -358,10 +360,11 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params, get_namespace_name(RelationGetNamespace(onerel)), RelationGetRelationName(onerel), vacrelstats->num_index_scans); - appendStringInfo(&buf, _("pages: %u removed, %u remain, %u skipped due to pins\n"), + appendStringInfo(&buf, _("pages: %u removed, %u remain, %u skipped due to pins, %u skipped frozen\n"), vacrelstats->pages_removed, vacrelstats->rel_pages, - vacrelstats->pinskipped_pages); + vacrelstats->pinskipped_pages, + vacrelstats->frozenskipped_pages); appendStringInfo(&buf, _("tuples: %.0f removed, %.0f remain, %.0f are dead but not yet removable\n"), vacrelstats->tuples_deleted, @@ -434,7 +437,7 @@ vacuum_log_cleanup_info(Relation rel, LVRelStats *vacrelstats) */ static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, - Relation *Irel, int nindexes, bool scan_all) + Relation *Irel, int nindexes, bool aggressive) { BlockNumber nblocks, blkno; @@ -450,8 +453,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, int i; PGRUsage ru0; Buffer vmbuffer = InvalidBuffer; - BlockNumber next_not_all_visible_block; - bool skipping_all_visible_blocks; + BlockNumber next_unskippable_block; + bool skipping_blocks; xl_heap_freeze_tuple *frozen; StringInfoData buf; @@ -479,35 +482,39 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage); /* - * We want to skip pages that don't require vacuuming according to the - * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD - * consecutive pages. Since we're reading sequentially, the OS should be - * doing readahead for us, so there's no gain in skipping a page now and - * then; that's likely to disable readahead and so be counterproductive. - * Also, skipping even a single page means that we can't update - * relfrozenxid, so we only want to do it if we can skip a goodly number - * of pages. + * Except when aggressive is set, we want to skip pages that are + * all-visible according to the visibility map, but only when we can skip + * at least SKIP_PAGES_THRESHOLD consecutive pages. Since we're reading + * sequentially, the OS should be doing readahead for us, so there's no + * gain in skipping a page now and then; that's likely to disable + * readahead and so be counterproductive. Also, skipping even a single + * page means that we can't update relfrozenxid, so we only want to do it + * if we can skip a goodly number of pages. * - * Before entering the main loop, establish the invariant that - * next_not_all_visible_block is the next block number >= blkno that's not - * all-visible according to the visibility map, or nblocks if there's no - * such block. Also, we set up the skipping_all_visible_blocks flag, - * which is needed because we need hysteresis in the decision: once we've - * started skipping blocks, we may as well skip everything up to the next - * not-all-visible block. + * When aggressive is set, we can't skip pages just because they are + * all-visible, but we can still skip pages that are all-frozen, since + * such pages do not need freezing and do not affect the value that we can + * safely set for relfrozenxid or relminmxid. * - * Note: if scan_all is true, we won't actually skip any pages; but we - * maintain next_not_all_visible_block anyway, so as to set up the - * all_visible_according_to_vm flag correctly for each page. + * Before entering the main loop, establish the invariant that + * next_unskippable_block is the next block number >= blkno that's not we + * can't skip based on the visibility map, either all-visible for a + * regular scan or all-frozen for an aggressive scan. We set it to + * nblocks if there's no such block. We also set up the skipping_blocks + * flag correctly at this stage. * * Note: The value returned by visibilitymap_get_status could be slightly * out-of-date, since we make this test before reading the corresponding * heap page or locking the buffer. This is OK. If we mistakenly think - * that the page is all-visible when in fact the flag's just been cleared, - * we might fail to vacuum the page. But it's OK to skip pages when - * scan_all is not set, so no great harm done; the next vacuum will find - * them. If we make the reverse mistake and vacuum a page unnecessarily, - * it'll just be a no-op. + * that the page is all-visible or all-frozen when in fact the flag's just + * been cleared, we might fail to vacuum the page. It's easy to see that + * skipping a page when aggressive is not set is not a very big deal; we + * might leave some dead tuples lying around, but the next vacuum will + * find them. But even when aggressive *is* set, it's still OK if we miss + * a page whose all-frozen marking has just been cleared. Any new XIDs + * just added to that page are necessarily newer than the GlobalXmin we + * computed, so they'll have no effect on the value to which we can safely + * set relfrozenxid. A similar argument applies for MXIDs and relminmxid. * * We will scan the table's last page, at least to the extent of * determining whether it has tuples or not, even if it should be skipped @@ -518,18 +525,31 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, * the last page. This is worth avoiding mainly because such a lock must * be replayed on any hot standby, where it can be disruptive. */ - for (next_not_all_visible_block = 0; - next_not_all_visible_block < nblocks; - next_not_all_visible_block++) + for (next_unskippable_block = 0; + next_unskippable_block < nblocks; + next_unskippable_block++) { - if (!VM_ALL_VISIBLE(onerel, next_not_all_visible_block, &vmbuffer)) - break; + uint8 vmstatus; + + vmstatus = visibilitymap_get_status(onerel, next_unskippable_block, + &vmbuffer); + if (aggressive) + { + if ((vmstatus & VISIBILITYMAP_ALL_FROZEN) == 0) + break; + } + else + { + if ((vmstatus & VISIBILITYMAP_ALL_VISIBLE) == 0) + break; + } vacuum_delay_point(); } - if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD) - skipping_all_visible_blocks = true; + + if (next_unskippable_block >= SKIP_PAGES_THRESHOLD) + skipping_blocks = true; else - skipping_all_visible_blocks = false; + skipping_blocks = false; for (blkno = 0; blkno < nblocks; blkno++) { @@ -542,7 +562,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, int prev_dead_count; int nfrozen; Size freespace; - bool all_visible_according_to_vm; + bool all_visible_according_to_vm = false; bool all_visible; bool all_frozen = true; /* provided all_visible is also true */ bool has_dead_tuples; @@ -552,15 +572,28 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, #define FORCE_CHECK_PAGE() \ (blkno == nblocks - 1 && should_attempt_truncation(vacrelstats)) - if (blkno == next_not_all_visible_block) + if (blkno == next_unskippable_block) { - /* Time to advance next_not_all_visible_block */ - for (next_not_all_visible_block++; - next_not_all_visible_block < nblocks; - next_not_all_visible_block++) + /* Time to advance next_unskippable_block */ + for (next_unskippable_block++; + next_unskippable_block < nblocks; + next_unskippable_block++) { - if (!VM_ALL_VISIBLE(onerel, next_not_all_visible_block, &vmbuffer)) - break; + uint8 vmskipflags; + + vmskipflags = visibilitymap_get_status(onerel, + next_unskippable_block, + &vmbuffer); + if (aggressive) + { + if ((vmskipflags & VISIBILITYMAP_ALL_FROZEN) == 0) + break; + } + else + { + if ((vmskipflags & VISIBILITYMAP_ALL_VISIBLE) == 0) + break; + } vacuum_delay_point(); } @@ -569,17 +602,44 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, * skipping_all_visible_blocks to do the right thing at the * following blocks. */ - if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD) - skipping_all_visible_blocks = true; + if (next_unskippable_block - blkno > SKIP_PAGES_THRESHOLD) + skipping_blocks = true; else - skipping_all_visible_blocks = false; - all_visible_according_to_vm = false; + skipping_blocks = false; + + /* + * Normally, the fact that we can't skip this block must mean that + * it's not all-visible. But in an aggressive vacuum we know only + * that it's not all-frozen, so it might still be all-visible. + */ + if (aggressive && VM_ALL_VISIBLE(onerel, blkno, &vmbuffer)) + all_visible_according_to_vm = true; } else { - /* Current block is all-visible */ - if (skipping_all_visible_blocks && !scan_all && !FORCE_CHECK_PAGE()) + /* + * The current block is potentially skippable; if we've seen a + * long enough run of skippable blocks to justify skipping it, and + * we're not forced to check it, then go ahead and skip. + * Otherwise, the page must be at least all-visible if not + * all-frozen, so we can set all_visible_according_to_vm = true. + */ + if (skipping_blocks && !FORCE_CHECK_PAGE()) + { + /* + * Tricky, tricky. If this is in aggressive vacuum, the page + * must have been all-frozen at the time we checked whether it + * was skippable, but it might not be any more. We must be + * careful to count it as a skipped all-frozen page in that + * case, or else we'll think we can't update relfrozenxid and + * relminmxid. If it's not an aggressive vacuum, we don't + * know whether it was all-frozen, so we have to recheck; but + * in this case an approximate answer is OK. + */ + if (aggressive || VM_ALL_FROZEN(onerel, blkno, &vmbuffer)) + vacrelstats->frozenskipped_pages++; continue; + } all_visible_according_to_vm = true; } @@ -628,9 +688,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, * Pin the visibility map page in case we need to mark the page * all-visible. In most cases this will be very cheap, because we'll * already have the correct page pinned anyway. However, it's - * possible that (a) next_not_all_visible_block is covered by a - * different VM page than the current block or (b) we released our pin - * and did a cycle of index vacuuming. + * possible that (a) next_unskippable_block is covered by a different + * VM page than the current block or (b) we released our pin and did a + * cycle of index vacuuming. + * */ visibilitymap_pin(onerel, blkno, &vmbuffer); @@ -641,12 +702,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, if (!ConditionalLockBufferForCleanup(buf)) { /* - * If we're not scanning the whole relation to guard against XID + * If we're not performing an aggressive scan to guard against XID * wraparound, and we don't want to forcibly check the page, then * it's OK to skip vacuuming pages we get a lock conflict on. They * will be dealt with in some future vacuum. */ - if (!scan_all && !FORCE_CHECK_PAGE()) + if (!aggressive && !FORCE_CHECK_PAGE()) { ReleaseBuffer(buf); vacrelstats->pinskipped_pages++; @@ -663,7 +724,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, * ourselves for multiple buffers and then service whichever one * is received first. For now, this seems good enough. * - * If we get here with scan_all false, then we're just forcibly + * If we get here with aggressive false, then we're just forcibly * checking the page, and so we don't want to insist on getting * the lock; we only need to know if the page contains tuples, so * that we can update nonempty_pages correctly. It's convenient @@ -679,7 +740,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, vacrelstats->nonempty_pages = blkno + 1; continue; } - if (!scan_all) + if (!aggressive) { /* * Here, we must not advance scanned_pages; that would amount |