From 0f0432f1be115411602dc97400937cff42434f19 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Wed, 9 Jan 2019 09:22:20 +0900 Subject: [PATCH v3] Add DISABLE_INDEX_CLEANUP option to VACUUM command. --- doc/src/sgml/ref/vacuum.sgml | 19 +++++- src/backend/commands/vacuum.c | 8 ++- src/backend/commands/vacuumlazy.c | 128 ++++++++++++++++++++++++++++++----- src/backend/parser/gram.y | 2 + src/include/nodes/parsenodes.h | 4 +- src/test/regress/expected/vacuum.out | 2 + src/test/regress/sql/vacuum.sql | 2 + 7 files changed, 145 insertions(+), 20 deletions(-) diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index fd911f5..4ae0473 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -31,6 +31,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ table_and_columns is: @@ -161,7 +162,23 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ nonempty_pages = 0; vacrelstats->latestRemovedXid = InvalidTransactionId; - lazy_space_alloc(vacrelstats, nblocks); + lazy_space_alloc(vacrelstats, nblocks, disable_index_cleanup); frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage); /* Report that we're scanning the heap, advertising total # of blocks */ @@ -723,6 +729,8 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, }; int64 hvp_val[2]; + Assert(!disable_index_cleanup); + /* * Before beginning index vacuuming, we release any pin we may * hold on the visibility map page. This isn't necessary for @@ -1201,14 +1209,23 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, } /* - * If there are no indexes then we can vacuum the page right now - * instead of doing a second scan. + * If either there are no indexes or index cleanup is disabled then + * we can vacuum the page right now instead of doing a second scan. */ - if (nindexes == 0 && + if ((nindexes == 0 || disable_index_cleanup) && vacrelstats->num_dead_tuples > 0) { - /* Remove tuples from heap */ - lazy_vacuum_page(onerel, blkno, buf, 0, vacrelstats, &vmbuffer); + /* + * If index cleanup is disabled, we set the recorded tuples as + * dead, which removes its tuple storage but leaves ItemIDs. + * By leaving dead ItemIDs an index lookup can properly see the + * tuple is already dead. And the remained dead ItemIds will be + * removed by the next vacuum enabled the index cleanup. + */ + if (nindexes > 0 && disable_index_cleanup) + lazy_set_tuples_dead(onerel, blkno, buf, vacrelstats); + else + lazy_vacuum_page(onerel, blkno, buf, 0, vacrelstats, &vmbuffer); has_dead_tuples = false; /* @@ -1374,6 +1391,8 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, }; int64 hvp_val[2]; + Assert(!disable_index_cleanup); + /* Log cleanup info before we touch indexes */ vacuum_log_cleanup_info(onerel, vacrelstats); @@ -1412,15 +1431,24 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, 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 (!disable_index_cleanup) + 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) - ereport(elevel, - (errmsg("\"%s\": removed %.0f row versions in %u pages", - RelationGetRelationName(onerel), - tups_vacuumed, vacuumed_pages))); + { + if (disable_index_cleanup) + ereport(elevel, + (errmsg("\"%s\": marked %.0f row versions in %u pages as dead", + RelationGetRelationName(onerel), + tups_vacuumed, vacuumed_pages))); + else + ereport(elevel, + (errmsg("\"%s\": removed %.0f row versions in %u pages", + RelationGetRelationName(onerel), + tups_vacuumed, vacuumed_pages))); + } /* * This is pretty messy, but we split it up so that we can skip emitting @@ -1623,6 +1651,71 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, return tupindex; } + +/* + * lazy_set_tuples_dead() -- set tuples as dead on a page + * and repair its fragmentation. + * + * Unlike lazy_vacuum_page, this function sets the recorded tuples as dead + * instead of as unused, and doesn't update the page visibility. + * Also this function assumes that vacrelstats->dead_tuples has only dead + * tuples on the 'blkno' block. + * + * Caller must hold pin and buffer cleanup lock on the buffer. + */ +static void +lazy_set_tuples_dead(Relation onerel, BlockNumber blkno, Buffer buffer, + LVRelStats *vacrelstats) +{ + Page page = BufferGetPage(buffer); + int deadcnt = 0; + OffsetNumber dead[MaxOffsetNumber]; + int tupindex; + + pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, blkno); + + START_CRIT_SECTION(); + + for (tupindex = 0; tupindex < vacrelstats->num_dead_tuples; tupindex++) + { + OffsetNumber toff; + ItemId itemid; + + Assert(blkno == ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex])); + + toff = ItemPointerGetOffsetNumber(&vacrelstats->dead_tuples[tupindex]); + itemid = PageGetItemId(page, toff); + + if (ItemIdIsDead(itemid)) + continue; + + ItemIdSetDead(itemid); + dead[deadcnt++] = toff; + } + + PageRepairFragmentation(page); + + /* + * Mark buffer dirty before we write WAL. + */ + MarkBufferDirty(buffer); + + /* XLOG stuff */ + if (RelationNeedsWAL(onerel)) + { + XLogRecPtr recptr; + + recptr = log_heap_clean(onerel, buffer, + NULL, 0, + dead, deadcnt, + NULL, 0, + vacrelstats->latestRemovedXid); + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); +} + /* * lazy_check_needs_freeze() -- scan page to see if any tuples * need to be cleaned to avoid wraparound @@ -2079,14 +2172,15 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats) * See the comments at the head of this file for rationale. */ static void -lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks) +lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks, + bool disable_index_cleanup) { long maxtuples; int vac_work_mem = IsAutoVacuumWorkerProcess() && autovacuum_work_mem != -1 ? autovacuum_work_mem : maintenance_work_mem; - if (vacrelstats->hasindex) + if (vacrelstats->hasindex && !disable_index_cleanup) { maxtuples = (vac_work_mem * 1024L) / sizeof(ItemPointerData); maxtuples = Min(maxtuples, INT_MAX); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c086235..e688c04 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -10511,6 +10511,8 @@ vacuum_option_elem: { if (strcmp($1, "disable_page_skipping") == 0) $$ = VACOPT_DISABLE_PAGE_SKIPPING; + else if (strcmp($1, "disable_index_cleanup") == 0) + $$ = VACOPT_DISABLE_INDEX_CLEANUP; else if (strcmp($1, "skip_locked") == 0) $$ = VACOPT_SKIP_LOCKED; else diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 27782fe..8266eaf 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3152,7 +3152,9 @@ typedef enum VacuumOption VACOPT_FULL = 1 << 4, /* FULL (non-concurrent) vacuum */ VACOPT_SKIP_LOCKED = 1 << 5, /* skip if cannot get lock */ VACOPT_SKIPTOAST = 1 << 6, /* don't process the TOAST table, if any */ - VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */ + VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7, /* don't skip any pages */ + VACOPT_DISABLE_INDEX_CLEANUP = 1 << 8 /* don't remove dead tuple and + * cleanup indexes */ } VacuumOption; /* diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out index fa9d663..bf3e50e 100644 --- a/src/test/regress/expected/vacuum.out +++ b/src/test/regress/expected/vacuum.out @@ -80,6 +80,8 @@ CONTEXT: SQL function "do_analyze" statement 1 SQL function "wrap_do_analyze" statement 1 VACUUM FULL vactst; VACUUM (DISABLE_PAGE_SKIPPING) vaccluster; +VACUUM (DISABLE_INDEX_CLEANUP) vaccluster, vactst; +VACUUM (DISABLE_INDEX_CLEANUP, FREEZE) vaccluster, vactst; -- partitioned table CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a); CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1); diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql index 9defa0d..a1beafc 100644 --- a/src/test/regress/sql/vacuum.sql +++ b/src/test/regress/sql/vacuum.sql @@ -61,6 +61,8 @@ VACUUM FULL vaccluster; VACUUM FULL vactst; VACUUM (DISABLE_PAGE_SKIPPING) vaccluster; +VACUUM (DISABLE_INDEX_CLEANUP) vaccluster, vactst; +VACUUM (DISABLE_INDEX_CLEANUP, FREEZE) vaccluster, vactst; -- partitioned table CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a); -- 2.10.5