diff --git a/contrib/bloom/bloom.h b/contrib/bloom/bloom.h index 23aa7ac441..e07b71a336 100644 --- a/contrib/bloom/bloom.h +++ b/contrib/bloom/bloom.h @@ -201,6 +201,7 @@ extern void blendscan(IndexScanDesc scan); extern IndexBuildResult *blbuild(Relation heap, Relation index, struct IndexInfo *indexInfo); extern void blbuildempty(Relation index); +extern IndexVacuumStrategy blvacuumstrategy(IndexVacuumInfo *info); extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state); diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c index 26b9927c3a..4ea0cfc1d8 100644 --- a/contrib/bloom/blutils.c +++ b/contrib/bloom/blutils.c @@ -131,6 +131,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->ambuild = blbuild; amroutine->ambuildempty = blbuildempty; amroutine->aminsert = blinsert; + amroutine->amvacuumstrategy = blvacuumstrategy; amroutine->ambulkdelete = blbulkdelete; amroutine->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; diff --git a/contrib/bloom/blvacuum.c b/contrib/bloom/blvacuum.c index 3282adde03..32150493ee 100644 --- a/contrib/bloom/blvacuum.c +++ b/contrib/bloom/blvacuum.c @@ -23,6 +23,15 @@ #include "storage/lmgr.h" +/* + * Choose the vacuum strategy. Currently always do ambulkdelete. + */ +IndexVacuumStrategy +blvacuumstrategy(IndexVacuumInfo *info) +{ + return INDEX_VACUUM_BULKDELETE; +} + /* * Bulk deletion of all index entries pointing to a set of heap tuples. * The set of target tuples is specified via a callback routine that tells @@ -45,6 +54,13 @@ blbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, BloomMetaPageData *metaData; GenericXLogState *gxlogState; + /* + * Skip deleting index entries if the corresponding heap tuples will + * not be deleted. + */ + if (info->bulkdelete_skippable) + return NULL; + if (stats == NULL) stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); @@ -172,7 +188,7 @@ blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) BlockNumber npages, blkno; - if (info->analyze_only) + if (info->analyze_only || !info->vacuumcleanup_requested) return stats; if (stats == NULL) diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 1f72562c60..707c096e81 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -112,6 +112,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->ambuild = brinbuild; amroutine->ambuildempty = brinbuildempty; amroutine->aminsert = brininsert; + amroutine->amvacuumstrategy = brinvacuumstrategy; amroutine->ambulkdelete = brinbulkdelete; amroutine->amvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; @@ -770,10 +771,20 @@ brinbuildempty(Relation index) UnlockReleaseBuffer(metabuf); } +/* + * Choose the vacuum strategy. Currently always do ambulkdelete. + */ +IndexVacuumStrategy +brinvacuumstrategy(IndexVacuumInfo *info) +{ + return INDEX_VACUUM_BULKDELETE; +} + /* * brinbulkdelete * Since there are no per-heap-tuple index tuples in BRIN indexes, - * there's not a lot we can do here. + * there's not a lot we can do here regardless of + * info->bulkdelete_skippable. * * XXX we could mark item tuples as "dirty" (when a minimum or maximum heap * tuple is deleted), meaning the need to re-run summarization on the affected @@ -799,8 +810,11 @@ brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) { Relation heapRel; - /* No-op in ANALYZE ONLY mode */ - if (info->analyze_only) + /* + * No-op in ANALYZE ONLY mode or when user requests to disable index + * cleanup. + */ + if (info->analyze_only || !info->vacuumcleanup_requested) return stats; if (!stats) diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index ef9b56fd36..09d1cf5694 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -63,6 +63,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->ambuild = ginbuild; amroutine->ambuildempty = ginbuildempty; amroutine->aminsert = gininsert; + amroutine->amvacuumstrategy = ginvacuumstrategy; amroutine->ambulkdelete = ginbulkdelete; amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c index 0935a6d9e5..bcb804f3ce 100644 --- a/src/backend/access/gin/ginvacuum.c +++ b/src/backend/access/gin/ginvacuum.c @@ -560,6 +560,15 @@ ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint3 return (tmppage == origpage) ? NULL : tmppage; } +/* + * Choose the vacuum strategy. Currently always do ambulkdelete. + */ +IndexVacuumStrategy +ginvacuumstrategy(IndexVacuumInfo *info) +{ + return INDEX_VACUUM_BULKDELETE; +} + IndexBulkDeleteResult * ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state) @@ -571,6 +580,13 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))]; uint32 nRoot; + /* + * Skip deleting index entries if the corresponding heap tuples will + * not be deleted. + */ + if (info->bulkdelete_skippable) + return NULL; + gvs.tmpCxt = AllocSetContextCreate(CurrentMemoryContext, "Gin vacuum temporary context", ALLOCSET_DEFAULT_SIZES); @@ -708,6 +724,10 @@ ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) return stats; } + /* Skip index cleanup if user requests to disable */ + if (!info->vacuumcleanup_requested) + return stats; + /* * Set up all-zero stats and cleanup pending inserts if ginbulkdelete * wasn't called diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index 3f2b416ce1..f7d100255d 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -84,6 +84,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->ambuild = gistbuild; amroutine->ambuildempty = gistbuildempty; amroutine->aminsert = gistinsert; + amroutine->amvacuumstrategy = gistvacuumstrategy; amroutine->ambulkdelete = gistbulkdelete; amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c index a9c616c772..40ff75b1ad 100644 --- a/src/backend/access/gist/gistvacuum.c +++ b/src/backend/access/gist/gistvacuum.c @@ -52,6 +52,15 @@ static bool gistdeletepage(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, Buffer buffer, OffsetNumber downlink, Buffer leafBuffer); +/* + * Choose the vacuum strategy. Currently always do ambulkdelete. + */ +IndexVacuumStrategy +gistvacuumstrategy(IndexVacuumInfo *info) +{ + return INDEX_VACUUM_BULKDELETE; +} + /* * VACUUM bulkdelete stage: remove index entries. */ @@ -59,6 +68,13 @@ IndexBulkDeleteResult * gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state) { + /* + * Skip deleting index entries if the corresponding heap tuples will + * not be deleted. + */ + if (info->bulkdelete_skippable) + return NULL; + /* allocate stats if first time through, else re-use existing struct */ if (stats == NULL) stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); @@ -74,8 +90,11 @@ gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteResult * gistvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) { - /* No-op in ANALYZE ONLY mode */ - if (info->analyze_only) + /* + * No-op in ANALYZE ONLY mode or when user requests to disable index + * cleanup. + */ + if (info->analyze_only || !info->vacuumcleanup_requested) return stats; /* diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 7c9ccf446c..0ed2bd6717 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -81,6 +81,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->ambuild = hashbuild; amroutine->ambuildempty = hashbuildempty; amroutine->aminsert = hashinsert; + amroutine->amvacuumstrategy = hashvacuumstrategy; amroutine->ambulkdelete = hashbulkdelete; amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; @@ -443,6 +444,15 @@ hashendscan(IndexScanDesc scan) scan->opaque = NULL; } +/* + * Choose the vacuum strategy. Currently always do ambulkdelete. + */ +IndexVacuumStrategy +hashvacuumstrategy(IndexVacuumInfo *info) +{ + return INDEX_VACUUM_BULKDELETE; +} + /* * Bulk deletion of all index entries pointing to a set of heap tuples. * The set of target tuples is specified via a callback routine that tells @@ -468,6 +478,13 @@ hashbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, HashMetaPage metap; HashMetaPage cachedmetap; + /* + * Skip deleting index entries if the corresponding heap tuples will + * not be deleted. + */ + if (info->bulkdelete_skippable) + return NULL; + tuples_removed = 0; num_index_tuples = 0; diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 25f2d5df1b..93c4488e39 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -214,6 +214,18 @@ typedef struct LVShared double reltuples; bool estimated_count; + /* + * Copied from LVRelStats. It tells index AM that lazy vacuum will remove + * dead tuples from the heap after index vacuum. + */ + bool vacuum_heap; + + /* + * Copied from LVRelStats. It tells index AM whether amvacuumcleanup is + * requested or not. + */ + bool vacuumcleanup_requested; + /* * In single process lazy vacuum we could consume more memory during index * vacuuming or cleanup apart from the memory for heap scanning. In @@ -293,8 +305,8 @@ typedef struct LVRelStats { char *relnamespace; char *relname; - /* useindex = true means two-pass strategy; false means one-pass */ - bool useindex; + /* hasindex = true means two-pass strategy; false means one-pass */ + bool hasindex; /* Overall statistics about rel */ BlockNumber old_rel_pages; /* previous value of pg_class.relpages */ BlockNumber rel_pages; /* total number of pages */ @@ -310,9 +322,11 @@ typedef struct LVRelStats double tuples_deleted; BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */ LVDeadTuples *dead_tuples; + bool vacuum_heap; /* do we remove dead tuples from the heap? */ int num_index_scans; TransactionId latestRemovedXid; bool lock_waiter_detected; + bool vacuumcleanup_requested; /* INDEX_CLEANUP is set to false */ /* Used for error callback */ char *indname; @@ -343,6 +357,12 @@ static BufferAccessStrategy vac_strategy; static void lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, Relation *Irel, int nindexes, bool aggressive); +static void choose_vacuum_strategy(LVRelStats *vacrelstats, VacuumParams *params, + Relation *Irel, int nindexes); +static void lazy_vacuum_table_and_indexes(Relation onerel, VacuumParams *params, + LVRelStats *vacrelstats, Relation *Irel, + int nindexes, IndexBulkDeleteResult **stats, + LVParallelState *lps); static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats); static bool lazy_check_needs_freeze(Buffer buf, bool *hastup, LVRelStats *vacrelstats); @@ -442,7 +462,6 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params, ErrorContextCallback errcallback; Assert(params != NULL); - Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT); Assert(params->truncate != VACOPT_TERNARY_DEFAULT); /* not every AM requires these to be valid, but heap does */ @@ -501,8 +520,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params, /* Open all indexes of the relation */ vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel); - vacrelstats->useindex = (nindexes > 0 && - params->index_cleanup == VACOPT_TERNARY_ENABLED); + vacrelstats->hasindex = (nindexes > 0); /* * Setup error traceback support for ereport(). The idea is to set up an @@ -811,14 +829,23 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, vacrelstats->nonempty_pages = 0; vacrelstats->latestRemovedXid = InvalidTransactionId; + /* index vacuum cleanup is enabled if index cleanup is not + * disabled, i.g., either default or enabled. + */ + vacrelstats->vacuumcleanup_requested = + (params->index_cleanup != VACOPT_TERNARY_DISABLED); + vistest = GlobalVisTestFor(onerel); /* * Initialize state for a parallel vacuum. As of now, only one worker can * be used for an index, so we invoke parallelism only if there are at - * least two indexes on a table. + * least two indexes on a table. When the index cleanup is disabled, + * since index bulk-deletions are likely to be no-op we disable a parallel + * vacuum. */ - if (params->nworkers >= 0 && vacrelstats->useindex && nindexes > 1) + if (params->nworkers >= 0 && nindexes > 1 && + params->index_cleanup != VACOPT_TERNARY_DISABLED) { /* * Since parallel workers cannot access data in temporary tables, we @@ -1050,19 +1077,10 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, vmbuffer = InvalidBuffer; } - /* Work on all the indexes, then the heap */ - lazy_vacuum_all_indexes(onerel, Irel, indstats, - vacrelstats, lps, nindexes); - - /* Remove tuples from heap */ - lazy_vacuum_heap(onerel, vacrelstats); - - /* - * Forget the now-vacuumed tuples, and press on, but be careful - * not to reset latestRemovedXid since we want that value to be - * valid. - */ - dead_tuples->num_tuples = 0; + /* Vacuum the table and its indexes */ + lazy_vacuum_table_and_indexes(onerel, params, vacrelstats, + Irel, nindexes, indstats, + lps); /* * Vacuum the Free Space Map to make newly-freed space visible on @@ -1515,29 +1533,14 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, * doing a second scan. Also we don't do that but forget dead tuples * when index cleanup is disabled. */ - if (!vacrelstats->useindex && dead_tuples->num_tuples > 0) + if (!vacrelstats->hasindex && dead_tuples->num_tuples > 0) { - if (nindexes == 0) - { - /* Remove tuples from heap if the table has no index */ - lazy_vacuum_page(onerel, blkno, buf, 0, vacrelstats, &vmbuffer); - vacuumed_pages++; - has_dead_tuples = false; - } - else - { - /* - * 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. - */ - Assert(params->index_cleanup == VACOPT_TERNARY_DISABLED); - } + Assert(nindexes == 0); + + /* Remove tuples from heap if the table has no index */ + lazy_vacuum_page(onerel, blkno, buf, 0, vacrelstats, &vmbuffer); + vacuumed_pages++; + has_dead_tuples = false; /* * Forget the now-vacuumed tuples, and press on, but be careful @@ -1702,14 +1705,9 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, /* If any tuples need to be deleted, perform final vacuum cycle */ /* XXX put a threshold on min number of tuples here? */ if (dead_tuples->num_tuples > 0) - { - /* Work on all the indexes, and then the heap */ - lazy_vacuum_all_indexes(onerel, Irel, indstats, vacrelstats, - lps, nindexes); - - /* Remove tuples from heap */ - lazy_vacuum_heap(onerel, vacrelstats); - } + lazy_vacuum_table_and_indexes(onerel, params, vacrelstats, + Irel, nindexes, indstats, + lps); /* * Vacuum the remainder of the Free Space Map. We must do this whether or @@ -1722,7 +1720,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, blkno); /* Do post-vacuum cleanup */ - if (vacrelstats->useindex) + if (vacrelstats->hasindex) lazy_cleanup_all_indexes(Irel, indstats, vacrelstats, lps, nindexes); /* @@ -1775,6 +1773,103 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, pfree(buf.data); } +/* + * Remove the collected garbage tuples from the table and its indexes. + */ +static void +lazy_vacuum_table_and_indexes(Relation onerel, VacuumParams *params, + LVRelStats *vacrelstats, Relation *Irel, + int nindexes, IndexBulkDeleteResult **indstats, + LVParallelState *lps) +{ + /* + * Choose the vacuum strategy for this vacuum cycle. + * choose_vacuum_strategy will set the decision to + * vacrelstats->vacuum_heap. + */ + choose_vacuum_strategy(vacrelstats, params, Irel, nindexes); + + /* Work on all the indexes, then the heap */ + lazy_vacuum_all_indexes(onerel, Irel, indstats, vacrelstats, lps, + nindexes); + + if (vacrelstats->vacuum_heap) + { + /* Remove tuples from heap */ + lazy_vacuum_heap(onerel, vacrelstats); + } + else + { + /* + * Here, we don't do heap vacuum in this cycle. + * + * 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. + */ + Assert(params->index_cleanup != VACOPT_TERNARY_ENABLED); + } + + /* + * Forget the now-vacuumed tuples, and press on, but be careful + * not to reset latestRemovedXid since we want that value to be + * valid. + */ + vacrelstats->dead_tuples->num_tuples = 0; +} + +/* + * Decide whether or not we remove the collected garbage tuples from the + * heap. + */ +static void +choose_vacuum_strategy(LVRelStats *vacrelstats, VacuumParams *params, + Relation *Irel, int nindexes) +{ + bool vacuum_heap = true; + + /* + * If index cleanup option is specified, we use it. + * + * XXX: should we call amvacuumstrategy even if INDEX_CLEANUP + * is specified? + */ + if (params->index_cleanup == VACOPT_TERNARY_ENABLED) + vacuum_heap = true; + else if (params->index_cleanup == VACOPT_TERNARY_DISABLED) + vacuum_heap = false; + else + { + int i; + + /* + * If index cleanup option is not specified, we decide the vacuum + * strategy based on the returned values from amvacuumstrategy. + * If even one index returns 'none', we skip heap vacuum in this + * vacuum cycle. + */ + for (i = 0; i < nindexes; i++) + { + IndexVacuumStrategy ivacstrat; + IndexVacuumInfo ivinfo; + + ivinfo.index = Irel[i]; + /* XXX: fill other fields */ + + ivacstrat = index_vacuum_strategy(&ivinfo); + + if (ivacstrat == INDEX_VACUUM_NONE) + { + vacuum_heap = false; + break; + } + } + } + + vacrelstats->vacuum_heap = vacuum_heap; +} + /* * lazy_vacuum_all_indexes() -- vacuum all indexes of relation. * @@ -2120,6 +2215,10 @@ lazy_parallel_vacuum_indexes(Relation *Irel, IndexBulkDeleteResult **stats, */ nworkers = Min(nworkers, lps->pcxt->nworkers); + /* Copy the information to the shared state */ + lps->lvshared->vacuum_heap = vacrelstats->vacuum_heap; + lps->lvshared->vacuumcleanup_requested = vacrelstats->vacuumcleanup_requested; + /* Setup the shared cost-based vacuum delay and launch workers */ if (nworkers > 0) { @@ -2444,6 +2543,13 @@ lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, ivinfo.message_level = elevel; ivinfo.num_heap_tuples = reltuples; ivinfo.strategy = vac_strategy; + ivinfo.vacuumcleanup_requested = vacrelstats->vacuumcleanup_requested; + + /* + * index bulk-deletion can be skipped safely if we won't delete + * garbage tuples from the heap. + */ + ivinfo.bulkdelete_skippable = !(vacrelstats->vacuum_heap); /* * Update error traceback information. @@ -2461,11 +2567,16 @@ lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, *stats = index_bulk_delete(&ivinfo, *stats, lazy_tid_reaped, (void *) dead_tuples); - ereport(elevel, - (errmsg("scanned index \"%s\" to remove %d row versions", - vacrelstats->indname, - dead_tuples->num_tuples), - errdetail_internal("%s", pg_rusage_show(&ru0)))); + /* + * XXX: we don't want to report if ambulkdelete was no-op because of + * bulkdelete_skippable. But we cannot know it was or not. + */ + if (*stats) + ereport(elevel, + (errmsg("scanned index \"%s\" to remove %d row versions", + vacrelstats->indname, + dead_tuples->num_tuples), + errdetail_internal("%s", pg_rusage_show(&ru0)))); /* Revert to the previous phase information for error traceback */ restore_vacuum_error_info(vacrelstats, &saved_err_info); @@ -2495,9 +2606,10 @@ lazy_cleanup_index(Relation indrel, ivinfo.report_progress = false; ivinfo.estimated_count = estimated_count; ivinfo.message_level = elevel; - ivinfo.num_heap_tuples = reltuples; ivinfo.strategy = vac_strategy; + ivinfo.bulkdelete_skippable = false; + ivinfo.vacuumcleanup_requested = vacrelstats->vacuumcleanup_requested; /* * Update error traceback information. @@ -2844,14 +2956,14 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats) * Return the maximum number of dead tuples we can record. */ static long -compute_max_dead_tuples(BlockNumber relblocks, bool useindex) +compute_max_dead_tuples(BlockNumber relblocks, bool hasindex) { long maxtuples; int vac_work_mem = IsAutoVacuumWorkerProcess() && autovacuum_work_mem != -1 ? autovacuum_work_mem : maintenance_work_mem; - if (useindex) + if (hasindex) { maxtuples = MAXDEADTUPLES(vac_work_mem * 1024L); maxtuples = Min(maxtuples, INT_MAX); @@ -2881,7 +2993,7 @@ lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks) LVDeadTuples *dead_tuples = NULL; long maxtuples; - maxtuples = compute_max_dead_tuples(relblocks, vacrelstats->useindex); + maxtuples = compute_max_dead_tuples(relblocks, vacrelstats->hasindex); dead_tuples = (LVDeadTuples *) palloc(SizeOfDeadTuples(maxtuples)); dead_tuples->num_tuples = 0; @@ -3573,6 +3685,9 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc) vacrelstats.indname = NULL; vacrelstats.phase = VACUUM_ERRCB_PHASE_UNKNOWN; /* Not yet processing */ + vacrelstats.vacuum_heap = lvshared->vacuum_heap; + vacrelstats.vacuumcleanup_requested = lvshared->vacuumcleanup_requested; + /* Setup error traceback support for ereport() */ errcallback.callback = vacuum_error_callback; errcallback.arg = &vacrelstats; diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index 3fb8688f8f..8df683c640 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -676,6 +676,25 @@ index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap) return ntids; } +/* ---------------- + * index_vacuum_strategy - decide whether or not to bulkdelete + * + * This callback routine is called just before calling ambulkdelete. + * Returns IndexVacuumStrategy to tell the lazy vacuum whether we do + * bulkdelete. + * ---------------- + */ +IndexVacuumStrategy +index_vacuum_strategy(IndexVacuumInfo *info) +{ + Relation indexRelation = info->index; + + RELATION_CHECKS; + CHECK_REL_PROCEDURE(amvacuumstrategy); + + return indexRelation->rd_indam->amvacuumstrategy(info); +} + /* ---------------- * index_bulk_delete - do mass deletion of index entries * diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 0abec10798..38d6a60199 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -133,6 +133,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->ambuild = btbuild; amroutine->ambuildempty = btbuildempty; amroutine->aminsert = btinsert; + amroutine->amvacuumstrategy = btvacuumstrategy; amroutine->ambulkdelete = btbulkdelete; amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; @@ -821,6 +822,18 @@ _bt_vacuum_needs_cleanup(IndexVacuumInfo *info) */ result = true; } + else if (!info->vacuumcleanup_requested) + { + /* + * Skip cleanup if INDEX_CLEANUP is set to false, even if there might + * be a deleted page that can be recycled. If INDEX_CLEANUP continues + * to be disabled, recyclable pages could be left by XID wraparound. + * But in practice it's not so harmful since such workload doesn't need + * to delete and recycle pages in any case and deletion of btree index + * pages is relatively rare. + */ + result = false; + } else if (TransactionIdIsValid(metad->btm_oldest_btpo_xact) && GlobalVisCheckRemovableXid(NULL, metad->btm_oldest_btpo_xact)) { @@ -863,6 +876,15 @@ _bt_vacuum_needs_cleanup(IndexVacuumInfo *info) return result; } +/* + * Choose the vacuum strategy. Currently always do ambulkdelete. + */ +IndexVacuumStrategy +btvacuumstrategy(IndexVacuumInfo *info) +{ + return INDEX_VACUUM_BULKDELETE; +} + /* * Bulk deletion of all index entries pointing to a set of heap tuples. * The set of target tuples is specified via a callback routine that tells @@ -877,6 +899,13 @@ btbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, Relation rel = info->index; BTCycleId cycleid; + /* + * Skip deleting index entries if the corresponding heap tuples will + * not be deleted. + */ + if (info->bulkdelete_skippable) + return NULL; + /* allocate stats if first time through, else re-use existing struct */ if (stats == NULL) stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index 64d3ba8288..b18858a50e 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -66,6 +66,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->ambuild = spgbuild; amroutine->ambuildempty = spgbuildempty; amroutine->aminsert = spginsert; + amroutine->amvacuumstrategy = spgvacuumstrategy; amroutine->ambulkdelete = spgbulkdelete; amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c index e1c58933f9..9aafcf9347 100644 --- a/src/backend/access/spgist/spgvacuum.c +++ b/src/backend/access/spgist/spgvacuum.c @@ -894,6 +894,15 @@ spgvacuumscan(spgBulkDeleteState *bds) bds->stats->pages_free = bds->stats->pages_deleted; } +/* + * Choose the vacuum strategy. Currently always do ambulkdelete. + */ +IndexVacuumStrategy +spgvacuumstrategy(IndexVacuumInfo *info) +{ + return INDEX_VACUUM_BULKDELETE; +} + /* * Bulk deletion of all index entries pointing to a set of heap tuples. * The set of target tuples is specified via a callback routine that tells @@ -907,6 +916,13 @@ spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, { spgBulkDeleteState bds; + /* + * Skip deleting index entries if the corresponding heap tuples will + * not be deleted. + */ + if (info->bulkdelete_skippable) + return NULL; + /* allocate stats if first time through, else re-use existing struct */ if (stats == NULL) stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); @@ -937,8 +953,11 @@ spgvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) { spgBulkDeleteState bds; - /* No-op in ANALYZE ONLY mode */ - if (info->analyze_only) + /* + * No-op in ANALYZE ONLY mode or when user requests to disable index + * cleanup. + */ + if (info->analyze_only || !info->vacuumcleanup_requested) return stats; /* diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 731610c701..abd8d1844e 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -3401,6 +3401,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot) ivinfo.message_level = DEBUG2; ivinfo.num_heap_tuples = heapRelation->rd_rel->reltuples; ivinfo.strategy = NULL; + ivinfo.bulkdelete_skippable = false; + ivinfo.vacuumcleanup_requested = true; /* * Encode TIDs as int8 values for the sort, rather than directly sorting diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 8af12b5c6b..4e46e920cf 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -668,6 +668,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params, ivinfo.message_level = elevel; ivinfo.num_heap_tuples = onerel->rd_rel->reltuples; ivinfo.strategy = vac_strategy; + ivinfo.vacuumcleanup_requested = true; stats = index_vacuum_cleanup(&ivinfo, NULL); diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 98270a1049..6a182ba9cd 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -1870,14 +1870,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) onerelid = onerel->rd_lockInfo.lockRelId; LockRelationIdForSession(&onerelid, lmode); - /* Set index cleanup option based on reloptions if not yet */ + /* Set index cleanup option if either reloptions or INDEX_CLEANUP vacuum + * command option is set. + */ if (params->index_cleanup == VACOPT_TERNARY_DEFAULT) { - if (onerel->rd_options == NULL || - ((StdRdOptions *) onerel->rd_options)->vacuum_index_cleanup) - params->index_cleanup = VACOPT_TERNARY_ENABLED; - else - params->index_cleanup = VACOPT_TERNARY_DISABLED; + if (onerel->rd_options != NULL) + { + if (((StdRdOptions *) onerel->rd_options)->vacuum_index_cleanup) + params->index_cleanup = VACOPT_TERNARY_ENABLED; + else + params->index_cleanup = VACOPT_TERNARY_DISABLED; + } } /* Set truncate option based on reloptions if not yet */ diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 85b4766016..f885c6ac67 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -111,6 +111,8 @@ typedef bool (*aminsert_function) (Relation indexRelation, Relation heapRelation, IndexUniqueCheck checkUnique, struct IndexInfo *indexInfo); +/* vacuum strategy */ +typedef IndexVacuumStrategy (*amvacuumstrategy_function) (IndexVacuumInfo *info); /* bulk delete */ typedef IndexBulkDeleteResult *(*ambulkdelete_function) (IndexVacuumInfo *info, @@ -258,6 +260,7 @@ typedef struct IndexAmRoutine ambuild_function ambuild; ambuildempty_function ambuildempty; aminsert_function aminsert; + amvacuumstrategy_function amvacuumstrategy; ambulkdelete_function ambulkdelete; amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h index 9ffc9100c0..cdf98489cf 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -97,6 +97,7 @@ extern int64 bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm); extern void brinrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, ScanKey orderbys, int norderbys); extern void brinendscan(IndexScanDesc scan); +extern IndexVacuumStrategy brinvacuumstrategy(IndexVacuumInfo *info); extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, diff --git a/src/include/access/genam.h b/src/include/access/genam.h index 68d90f5141..eea3a28411 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.h @@ -34,7 +34,8 @@ typedef struct IndexBuildResult } IndexBuildResult; /* - * Struct for input arguments passed to ambulkdelete and amvacuumcleanup + * Struct for input arguments passed to amvacuumstrategy, ambulkdelete + * and amvacuumcleanup * * num_heap_tuples is accurate only when estimated_count is false; * otherwise it's just an estimate (currently, the estimate is the @@ -47,6 +48,22 @@ typedef struct IndexVacuumInfo bool analyze_only; /* ANALYZE (without any actual vacuum) */ bool report_progress; /* emit progress.h status reports */ bool estimated_count; /* num_heap_tuples is an estimate */ + + /* + * Is this ambulkdelete call is skippable? If true, since lazy vacuum + * won't delete the garbage tuples from the heap, the index AM can + * skip index bulk-deletion safely. This field is used only when + * ambulkdelete. + */ + bool bulkdelete_skippable; + + /* + * amvacuumcleanup is requested by lazy vacuum. If false, the index AM + * can skip index cleanup. This can be false if INDEX_CLEANUP vacuum option + * is set to false. This field is used only when amvacuumcleanup. + */ + bool vacuumcleanup_requested; + int message_level; /* ereport level for progress messages */ double num_heap_tuples; /* tuples remaining in heap */ BufferAccessStrategy strategy; /* access strategy for reads */ @@ -125,6 +142,13 @@ typedef struct IndexOrderByDistance bool isnull; } IndexOrderByDistance; +/* Result value for amvacuumstrategy */ +typedef enum IndexVacuumStrategy +{ + INDEX_VACUUM_NONE, /* No-op, skip bulk-deletion in this vacuum cycle */ + INDEX_VACUUM_BULKDELETE /* Do ambulkdelete */ +} IndexVacuumStrategy; + /* * generalized index_ interface routines (in indexam.c) */ @@ -173,6 +197,7 @@ extern bool index_getnext_slot(IndexScanDesc scan, ScanDirection direction, struct TupleTableSlot *slot); extern int64 index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap); +extern IndexVacuumStrategy index_vacuum_strategy(IndexVacuumInfo *info); extern IndexBulkDeleteResult *index_bulk_delete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h index 5cb2f72e4c..21e7282e36 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -396,6 +396,7 @@ extern int64 gingetbitmap(IndexScanDesc scan, TIDBitmap *tbm); extern void ginInitConsistentFunction(GinState *ginstate, GinScanKey key); /* ginvacuum.c */ +extern IndexVacuumStrategy ginvacuumstrategy(IndexVacuumInfo *info); extern IndexBulkDeleteResult *ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h index b68c01a5f2..3d191f241d 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -532,6 +532,7 @@ extern void gistMakeUnionKey(GISTSTATE *giststate, int attno, extern XLogRecPtr gistGetFakeLSN(Relation rel); /* gistvacuum.c */ +extern IndexVacuumStrategy gistvacuumstrategy(IndexVacuumInfo *info); extern IndexBulkDeleteResult *gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, diff --git a/src/include/access/hash.h b/src/include/access/hash.h index bab4d9f1b0..a9b99a6fa3 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -371,6 +371,7 @@ extern IndexScanDesc hashbeginscan(Relation rel, int nkeys, int norderbys); extern void hashrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, ScanKey orderbys, int norderbys); extern void hashendscan(IndexScanDesc scan); +extern IndexVacuumStrategy hashvacuumstrategy(IndexVacuumInfo *info); extern IndexBulkDeleteResult *hashbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index e8fecc6026..7f74066b44 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1008,6 +1008,7 @@ extern void btparallelrescan(IndexScanDesc scan); extern void btendscan(IndexScanDesc scan); extern void btmarkpos(IndexScanDesc scan); extern void btrestrpos(IndexScanDesc scan); +extern IndexVacuumStrategy btvacuumstrategy(IndexVacuumInfo *info); extern IndexBulkDeleteResult *btbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h index 9f2ccc1730..33cc62f489 100644 --- a/src/include/access/spgist.h +++ b/src/include/access/spgist.h @@ -211,6 +211,7 @@ extern bool spggettuple(IndexScanDesc scan, ScanDirection dir); extern bool spgcanreturn(Relation index, int attno); /* spgvacuum.c */ +extern IndexVacuumStrategy spgvacuumstrategy(IndexVacuumInfo *info); extern IndexBulkDeleteResult *spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index a4cd721400..d96e6b6239 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -218,8 +218,10 @@ typedef struct VacuumParams int log_min_duration; /* minimum execution threshold in ms at * which verbose logs are activated, -1 * to use default */ - VacOptTernaryValue index_cleanup; /* Do index vacuum and cleanup, - * default value depends on reloptions */ + VacOptTernaryValue index_cleanup; /* Do index vacuum and cleanup. In + * default mode, it's decided based on + * multiple factors. See + * choose_vacuum_strategy. */ VacOptTernaryValue truncate; /* Truncate empty pages at the end, * default value depends on reloptions */