poc_vacuumstrategy.patch
application/octet-stream
Filename: poc_vacuumstrategy.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| contrib/bloom/bloom.h | 1 | 0 |
| contrib/bloom/blutils.c | 1 | 0 |
| contrib/bloom/blvacuum.c | 17 | 1 |
| src/backend/access/brin/brin.c | 17 | 3 |
| src/backend/access/gin/ginutil.c | 1 | 0 |
| src/backend/access/gin/ginvacuum.c | 20 | 0 |
| src/backend/access/gist/gist.c | 1 | 0 |
| src/backend/access/gist/gistvacuum.c | 21 | 2 |
| src/backend/access/hash/hash.c | 17 | 0 |
| src/backend/access/heap/vacuumlazy.c | 175 | 60 |
| src/backend/access/index/indexam.c | 19 | 0 |
| src/backend/access/nbtree/nbtree.c | 29 | 0 |
| src/backend/access/spgist/spgutils.c | 1 | 0 |
| src/backend/access/spgist/spgvacuum.c | 21 | 2 |
| src/backend/catalog/index.c | 2 | 0 |
| src/backend/commands/analyze.c | 1 | 0 |
| src/backend/commands/vacuum.c | 10 | 6 |
| src/include/access/amapi.h | 3 | 0 |
| src/include/access/brin_internal.h | 1 | 0 |
| src/include/access/genam.h | 26 | 1 |
| src/include/access/gin_private.h | 1 | 0 |
| src/include/access/gist_private.h | 1 | 0 |
| src/include/access/hash.h | 1 | 0 |
| src/include/access/nbtree.h | 1 | 0 |
| src/include/access/spgist.h | 1 | 0 |
| src/include/commands/vacuum.h | 4 | 2 |
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 */