typedef struct LVRelStats
{
- /* hasindex = true means two-pass strategy; false means one-pass */
- bool hasindex;
+ /* useindex = true means two-pass strategy; false means one-pass */
+ bool useindex;
/* Overall statistics about rel */
BlockNumber old_rel_pages; /* previous value of pg_class.relpages */
BlockNumber rel_pages; /* total number of pages */
double new_rel_tuples; /* new estimated total # of tuples */
double new_live_tuples; /* new estimated total # of live tuples */
double new_dead_tuples; /* new estimated total # of dead tuples */
+ double nleft_dead_tuples; /* # of dead tuples we left */
+ double nleft_dead_itemids; /* # of dead item pointers we left */
BlockNumber pages_removed;
double tuples_deleted;
BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */
/* non-export function prototypes */
-static void lazy_scan_heap(Relation onerel, int options,
+static void lazy_scan_heap(Relation onerel, VacuumParams *params,
LVRelStats *vacrelstats, Relation *Irel, int nindexes,
bool aggressive);
static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats, BlockNumber nblocks);
MultiXactId new_min_multi;
Assert(params != NULL);
+ Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
/* measure elapsed time iff autovacuum logging requires it */
if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
/* Open all indexes of the relation */
vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel);
- vacrelstats->hasindex = (nindexes > 0);
+ vacrelstats->useindex = (nindexes > 0 &&
+ params->index_cleanup == VACOPT_TERNARY_ENABLED);
/* Do the vacuuming */
- lazy_scan_heap(onerel, params->options, vacrelstats, Irel, nindexes, aggressive);
+ lazy_scan_heap(onerel, params, vacrelstats, Irel, nindexes, aggressive);
/* Done with indexes */
vac_close_indexes(nindexes, Irel, NoLock);
new_rel_pages,
new_live_tuples,
new_rel_allvisible,
- vacrelstats->hasindex,
+ nindexes > 0,
new_frozen_xid,
new_min_multi,
false);
vacrelstats->new_rel_tuples,
vacrelstats->new_dead_tuples,
OldestXmin);
+ if (vacrelstats->nleft_dead_tuples > 0 ||
+ vacrelstats->nleft_dead_itemids > 0)
+ appendStringInfo(&buf,
+ _("%.0f tuples and %.0f item identifiers are left as dead.\n"),
+ vacrelstats->nleft_dead_tuples,
+ vacrelstats->nleft_dead_itemids);
appendStringInfo(&buf,
_("buffer usage: %d hits, %d misses, %d dirtied\n"),
VacuumPageHit,
* reference them have been killed.
*/
static void
-lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
+lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
Relation *Irel, int nindexes, bool aggressive)
{
BlockNumber nblocks,
live_tuples, /* live tuples (reltuples estimate) */
tups_vacuumed, /* tuples cleaned up by vacuum */
nkeep, /* dead-but-not-removable tuples */
- nunused; /* unused item pointers */
+ nunused, /* unused item pointers */
+ nleft_dead_tuples, /* tuples we left as dead */
+ nleft_dead_itemids; /* item pointers we left as dead,
+ * includes nleft_dead_tuples. */
IndexBulkDeleteResult **indstats;
int i;
PGRUsage ru0;
empty_pages = vacuumed_pages = 0;
next_fsm_block_to_vacuum = (BlockNumber) 0;
num_tuples = live_tuples = tups_vacuumed = nkeep = nunused = 0;
+ nleft_dead_itemids = nleft_dead_tuples = 0;
indstats = (IndexBulkDeleteResult **)
palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
* be replayed on any hot standby, where it can be disruptive.
*/
next_unskippable_block = 0;
- if ((options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
+ if ((params->options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
{
while (next_unskippable_block < nblocks)
{
{
/* Time to advance next_unskippable_block */
next_unskippable_block++;
- if ((options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
+ if ((params->options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
{
while (next_unskippable_block < nblocks)
{
HeapTupleIsHeapOnly(&tuple))
nkeep += 1;
else
+ {
tupgone = true; /* we can delete the tuple */
+
+ /*
+ * Since this dead tuple will not be vacuumed and
+ * ignored when index cleanup is disabled we count
+ * count it for reporting.
+ */
+ if (params->index_cleanup == VACOPT_TERNARY_ENABLED)
+ nleft_dead_tuples++;
+ }
all_visible = false;
break;
case HEAPTUPLE_LIVE:
}
/*
- * If there are no indexes then we can vacuum the page right now
- * instead of doing a second scan.
+ * If there are no indexes we can vacuum the page right now instead of
+ * doing a second scan. Also we don't do that but forget dead tuples
+ * when index cleanup is disabled.
*/
- if (nindexes == 0 &&
- vacrelstats->num_dead_tuples > 0)
+ if (!vacrelstats->useindex && vacrelstats->num_dead_tuples > 0)
{
- /* Remove tuples from heap */
- lazy_vacuum_page(onerel, blkno, buf, 0, vacrelstats, &vmbuffer);
- has_dead_tuples = false;
+ 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);
+ nleft_dead_itemids += vacrelstats->num_dead_tuples;
+ }
/*
* Forget the now-vacuumed tuples, and press on, but be careful
* valid.
*/
vacrelstats->num_dead_tuples = 0;
- vacuumed_pages++;
/*
* Periodically do incremental FSM vacuuming to make newly-freed
RecordPageWithFreeSpace(onerel, blkno, freespace, nblocks);
}
+ /* No dead tuples should be left if index cleanup is enabled */
+ Assert((params->index_cleanup == VACOPT_TERNARY_ENABLED &&
+ nleft_dead_tuples == 0 && nleft_dead_itemids == 0) ||
+ params->index_cleanup == VACOPT_TERNARY_DISABLED);
+
/* report that everything is scanned and vacuumed */
pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno);
/* save stats for use later */
vacrelstats->tuples_deleted = tups_vacuumed;
- vacrelstats->new_dead_tuples = nkeep;
+ vacrelstats->new_dead_tuples = nkeep + nleft_dead_tuples;
+ vacrelstats->nleft_dead_tuples = nleft_dead_tuples;
+ vacrelstats->nleft_dead_itemids = nleft_dead_itemids;
/* now we can compute the new value for pg_class.reltuples */
vacrelstats->new_live_tuples = vac_estimate_reltuples(onerel,
PROGRESS_VACUUM_PHASE_INDEX_CLEANUP);
/* Do post-vacuum cleanup and statistics update for each index */
- for (i = 0; i < nindexes; i++)
- lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
+ if (vacrelstats->useindex)
+ {
+ for (i = 0; i < nindexes; i++)
+ lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
+ }
/* If no indexes, make log report that lazy_vacuum_heap would've made */
if (vacuumed_pages)
"%u pages are entirely empty.\n",
empty_pages),
empty_pages);
+ appendStringInfo(&buf, "%.0f tuples and %.0f item identifiers are left as dead.\n",
+ nleft_dead_tuples, nleft_dead_itemids);
appendStringInfo(&buf, _("%s."), pg_rusage_show(&ru0));
ereport(elevel,
autovacuum_work_mem != -1 ?
autovacuum_work_mem : maintenance_work_mem;
- if (vacrelstats->hasindex)
+ if (vacrelstats->useindex)
{
maxtuples = (vac_work_mem * 1024L) / sizeof(ItemPointerData);
maxtuples = Min(maxtuples, INT_MAX);
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params);
+static VacOptTernaryValue get_vacopt_ternary_value(DefElem *def);
/*
* Primary entry point for manual VACUUM and ANALYZE commands
bool disable_page_skipping = false;
ListCell *lc;
+ /* Set default value */
+ params.index_cleanup = VACOPT_TERNARY_DEFAULT;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
full = defGetBoolean(opt);
else if (strcmp(opt->defname, "disable_page_skipping") == 0)
disable_page_skipping = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "index_cleanup") == 0)
+ params.index_cleanup = get_vacopt_ternary_value(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
onerelid = onerel->rd_lockInfo.lockRelId;
LockRelationIdForSession(&onerelid, lmode);
+ /* Set index cleanup option based on reloptions if not yet */
+ 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;
+ }
+
/*
* Remember the relation's TOAST relation for later, if the caller asked
* us to process it. In VACUUM FULL, though, the toast table is
CHECK_FOR_INTERRUPTS();
}
}
+
+/*
+ * A wrapper function of defGetBoolean().
+ *
+ * This function returns VACOPT_TERNARY_ENABLED and VACOPT_TERNARY_DISABLED
+ * instead of true and false.
+ */
+static VacOptTernaryValue
+get_vacopt_ternary_value(DefElem *def)
+{
+ return defGetBoolean(def) ? VACOPT_TERNARY_ENABLED : VACOPT_TERNARY_DISABLED;
+}