diff options
Diffstat (limited to 'src/backend/commands/vacuumlazy.c')
-rw-r--r-- | src/backend/commands/vacuumlazy.c | 169 |
1 files changed, 109 insertions, 60 deletions
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 9393fa0727a..ce5fa180662 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -77,17 +77,18 @@ * Before we consider skipping a page that's marked as clean in * visibility map, we must've seen at least this many clean pages. */ -#define SKIP_PAGES_THRESHOLD 32 +#define SKIP_PAGES_THRESHOLD ((BlockNumber) 32) typedef struct LVRelStats { /* hasindex = true means two-pass strategy; false means one-pass */ bool hasindex; - bool scanned_all; /* have we scanned all pages (this far)? */ /* Overall statistics about rel */ - BlockNumber rel_pages; + BlockNumber rel_pages; /* total number of pages */ + BlockNumber scanned_pages; /* number of pages we examined */ + double scanned_tuples; /* counts only tuples on scanned pages */ double old_rel_tuples; /* previous value of pg_class.reltuples */ - double rel_tuples; /* counts only tuples on scanned pages */ + double new_rel_tuples; /* new estimated total # of tuples */ BlockNumber pages_removed; double tuples_deleted; BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */ @@ -143,7 +144,7 @@ static int vac_cmp_itemptr(const void *left, const void *right); */ void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt, - BufferAccessStrategy bstrategy, bool *scanned_all) + BufferAccessStrategy bstrategy) { LVRelStats *vacrelstats; Relation *Irel; @@ -175,7 +176,6 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt, vacrelstats = (LVRelStats *) palloc0(sizeof(LVRelStats)); - vacrelstats->scanned_all = true; /* will be cleared if we skip a page */ vacrelstats->old_rel_tuples = onerel->rd_rel->reltuples; vacrelstats->num_index_scans = 0; @@ -205,24 +205,20 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt, FreeSpaceMapVacuum(onerel); /* - * Update statistics in pg_class. But only if we didn't skip any pages; - * the tuple count only includes tuples from the pages we've visited, and - * we haven't frozen tuples in unvisited pages either. The page count is - * accurate in any case, but because we use the reltuples / relpages ratio - * in the planner, it's better to not update relpages either if we can't - * update reltuples. + * Update statistics in pg_class. But don't change relfrozenxid if we + * skipped any pages. */ - if (vacrelstats->scanned_all) - vac_update_relstats(onerel, - vacrelstats->rel_pages, vacrelstats->rel_tuples, - vacrelstats->hasindex, - FreezeLimit); + vac_update_relstats(onerel, + vacrelstats->rel_pages, vacrelstats->new_rel_tuples, + vacrelstats->hasindex, + (vacrelstats->scanned_pages < vacrelstats->rel_pages) ? + InvalidTransactionId : + FreezeLimit); /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, - vacrelstats->scanned_all, - vacrelstats->rel_tuples); + vacrelstats->new_rel_tuples); /* and log the action if appropriate */ if (IsAutoVacuumWorkerProcess() && Log_autovacuum_min_duration >= 0) @@ -239,13 +235,12 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt, get_namespace_name(RelationGetNamespace(onerel)), RelationGetRelationName(onerel), vacrelstats->num_index_scans, - vacrelstats->pages_removed, vacrelstats->rel_pages, - vacrelstats->tuples_deleted, vacrelstats->rel_tuples, + vacrelstats->pages_removed, + vacrelstats->rel_pages, + vacrelstats->tuples_deleted, + vacrelstats->new_rel_tuples, pg_rusage_show(&ru0)))); } - - if (scanned_all) - *scanned_all = vacrelstats->scanned_all; } /* @@ -301,7 +296,6 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, HeapTupleData tuple; char *relname; BlockNumber empty_pages, - scanned_pages, vacuumed_pages; double num_tuples, tups_vacuumed, @@ -311,7 +305,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, int i; PGRUsage ru0; Buffer vmbuffer = InvalidBuffer; - BlockNumber all_visible_streak; + BlockNumber next_not_all_visible_block; + bool skipping_all_visible_blocks; pg_rusage_init(&ru0); @@ -321,7 +316,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, get_namespace_name(RelationGetNamespace(onerel)), relname))); - empty_pages = vacuumed_pages = scanned_pages = 0; + empty_pages = vacuumed_pages = 0; num_tuples = tups_vacuumed = nkeep = nunused = 0; indstats = (IndexBulkDeleteResult **) @@ -329,12 +324,47 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, nblocks = RelationGetNumberOfBlocks(onerel); vacrelstats->rel_pages = nblocks; + vacrelstats->scanned_pages = 0; vacrelstats->nonempty_pages = 0; vacrelstats->latestRemovedXid = InvalidTransactionId; lazy_space_alloc(vacrelstats, nblocks); - all_visible_streak = 0; + /* + * 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. + * + * 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. + * + * 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. + */ + for (next_not_all_visible_block = 0; + next_not_all_visible_block < nblocks; + next_not_all_visible_block++) + { + if (!visibilitymap_test(onerel, next_not_all_visible_block, &vmbuffer)) + break; + vacuum_delay_point(); + } + if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD) + skipping_all_visible_blocks = true; + else + skipping_all_visible_blocks = false; + for (blkno = 0; blkno < nblocks; blkno++) { Buffer buf; @@ -347,41 +377,45 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, OffsetNumber frozen[MaxOffsetNumber]; int nfrozen; Size freespace; - bool all_visible_according_to_vm = false; + bool all_visible_according_to_vm; bool all_visible; bool has_dead_tuples; - /* - * Skip pages that don't require vacuuming according to the visibility - * map. But only if we've seen a streak of at least - * SKIP_PAGES_THRESHOLD pages marked as clean. Since we're reading - * sequentially, the OS should be doing readahead for us and there's - * no gain in skipping a page now and then. You need a longer run of - * consecutive skipped pages before it's worthwhile. Also, skipping - * even a single page means that we can't update relfrozenxid or - * reltuples, so we only want to do it if there's a good chance to - * skip a goodly number of pages. - */ - if (!scan_all) + if (blkno == next_not_all_visible_block) { - all_visible_according_to_vm = - visibilitymap_test(onerel, blkno, &vmbuffer); - if (all_visible_according_to_vm) + /* 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++) { - all_visible_streak++; - if (all_visible_streak >= SKIP_PAGES_THRESHOLD) - { - vacrelstats->scanned_all = false; - continue; - } + if (!visibilitymap_test(onerel, next_not_all_visible_block, + &vmbuffer)) + break; + vacuum_delay_point(); } + + /* + * We know we can't skip the current block. But set up + * 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; else - all_visible_streak = 0; + skipping_all_visible_blocks = false; + all_visible_according_to_vm = false; + } + else + { + /* Current block is all-visible */ + if (skipping_all_visible_blocks && !scan_all) + continue; + all_visible_according_to_vm = true; } vacuum_delay_point(); - scanned_pages++; + vacrelstats->scanned_pages++; /* * If we are close to overrunning the available space for dead-tuple @@ -764,9 +798,15 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, } /* save stats for use later */ - vacrelstats->rel_tuples = num_tuples; + vacrelstats->scanned_tuples = num_tuples; vacrelstats->tuples_deleted = tups_vacuumed; + /* now we can compute the new value for pg_class.reltuples */ + vacrelstats->new_rel_tuples = vac_estimate_reltuples(onerel, false, + nblocks, + vacrelstats->scanned_pages, + num_tuples); + /* If any tuples need to be deleted, perform final vacuum cycle */ /* XXX put a threshold on min number of tuples here? */ if (vacrelstats->num_dead_tuples > 0) @@ -805,7 +845,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, ereport(elevel, (errmsg("\"%s\": found %.0f removable, %.0f nonremovable row versions in %u out of %u pages", RelationGetRelationName(onerel), - tups_vacuumed, num_tuples, scanned_pages, nblocks), + tups_vacuumed, num_tuples, + vacrelstats->scanned_pages, nblocks), errdetail("%.0f dead row versions cannot be removed yet.\n" "There were %.0f unused item pointers.\n" "%u pages are entirely empty.\n" @@ -977,10 +1018,9 @@ lazy_cleanup_index(Relation indrel, ivinfo.index = indrel; ivinfo.analyze_only = false; - ivinfo.estimated_count = !vacrelstats->scanned_all; + ivinfo.estimated_count = (vacrelstats->scanned_pages < vacrelstats->rel_pages); ivinfo.message_level = elevel; - /* use rel_tuples only if we scanned all pages, else fall back */ - ivinfo.num_heap_tuples = vacrelstats->scanned_all ? vacrelstats->rel_tuples : vacrelstats->old_rel_tuples; + ivinfo.num_heap_tuples = vacrelstats->new_rel_tuples; ivinfo.strategy = vac_strategy; stats = index_vacuum_cleanup(&ivinfo, stats); @@ -1041,8 +1081,13 @@ lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats) new_rel_pages = RelationGetNumberOfBlocks(onerel); if (new_rel_pages != old_rel_pages) { - /* might as well use the latest news when we update pg_class stats */ - vacrelstats->rel_pages = new_rel_pages; + /* + * Note: we intentionally don't update vacrelstats->rel_pages with + * the new rel size here. If we did, it would amount to assuming that + * the new pages are empty, which is unlikely. Leaving the numbers + * alone amounts to assuming that the new pages have the same tuple + * density as existing ones, which is less unlikely. + */ UnlockRelation(onerel, AccessExclusiveLock); return; } @@ -1076,7 +1121,11 @@ lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats) */ UnlockRelation(onerel, AccessExclusiveLock); - /* update statistics */ + /* + * Update statistics. Here, it *is* correct to adjust rel_pages without + * also touching reltuples, since the tuple count wasn't changed by the + * truncation. + */ vacrelstats->rel_pages = new_rel_pages; vacrelstats->pages_removed = old_rel_pages - new_rel_pages; |