diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 05221cc1d6..1abfdf06b7 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -153,6 +153,7 @@ #define PARALLEL_VACUUM_KEY_QUERY_TEXT 3 #define PARALLEL_VACUUM_KEY_BUFFER_USAGE 4 #define PARALLEL_VACUUM_KEY_WAL_USAGE 5 +#define PARALLEL_VACUUM_KEY_INDVAC_CHECK 6 /* used only when USE_ASSERT_CHECKING */ /* * Macro to check if we are in a parallel vacuum. If true, we are in the @@ -193,6 +194,20 @@ typedef struct LVDeadTuples #define MAXDEADTUPLES(max_size) \ (((max_size) - offsetof(LVDeadTuples, itemptrs)) / sizeof(ItemPointerData)) +/* + * LVIndexCheck stores an array of booleans that indicates whether or not + * N'th index is vacuumed (or cleaned up) or not. This is used to verify that + * all indexes are processed by parallel index vacuum. Therefore, this + * is used only when parallel vacuum and USE_ASSERT_CHECKING is defined. + */ +typedef struct LVIndVacCheck +{ + int nindexes; + bool processed[FLEXIBLE_ARRAY_MEMBER]; +} LVIndVacCheck; +#define SizeOfLVIndVacCheck(nindexes) \ + add_size(offsetof(LVIndVacCheck, processed), mul_size(sizeof(bool), (nindexes))) + /* * Shared information among parallel workers. So this is allocated in the DSM * segment. @@ -369,6 +384,12 @@ typedef struct LVRelState * table */ int64 num_tuples; /* total number of nonremovable tuples */ int64 live_tuples; /* live tuples (reltuples estimate) */ + + /* + * Information used to verify the result of parallel vacuum. This is + * always NULL if USE_ASSERT_CHECKING is undefined. + */ + LVIndVacCheck *indvac_check; } LVRelState; /* @@ -468,6 +489,11 @@ static void update_vacuum_error_info(LVRelState *vacrel, static void restore_vacuum_error_info(LVRelState *vacrel, const LVSavedErrInfo *saved_vacrel); +#ifdef USE_ASSERT_CHECKING +static void lazy_clear_indvac_check_info(LVIndVacCheck *indvac_check); +static void lazy_mark_index_processed(LVIndVacCheck *indvac_check, int idx); +static void lazy_verify_indvac_result(LVIndVacCheck *indvac_check); +#endif /* * heap_vacuum_rel() -- perform VACUUM for one heap relation @@ -2764,6 +2790,11 @@ do_parallel_vacuum_or_cleanup(LVRelState *vacrel, int nworkers) lps->pcxt->nworkers_launched, nworkers))); } +#ifdef USE_ASSERT_CHECKING + /* Clear index vacuum check info before actual vacuum processing */ + lazy_clear_indvac_check_info(vacrel->indvac_check); +#endif + /* Process the indexes that can be processed by only leader process */ do_serial_processing_for_unsafe_indexes(vacrel, lps->lvshared); @@ -2786,6 +2817,11 @@ do_parallel_vacuum_or_cleanup(LVRelState *vacrel, int nworkers) InstrAccumParallelQuery(&lps->buffer_usage[i], &lps->wal_usage[i]); } +#ifdef USE_ASSERT_CHECKING + /* Check if all indexes have been processed */ + lazy_verify_indvac_result(vacrel->indvac_check); +#endif + /* * Carry the shared balance value to heap scan and disable shared costing */ @@ -2847,6 +2883,10 @@ do_parallel_processing(LVRelState *vacrel, LVShared *lvshared) lvshared, shared_istat, vacrel); +#ifdef USE_ASSERT_CHECKING + /* Mark this index has been processed */ + lazy_mark_index_processed(vacrel->indvac_check, idx); +#endif } /* @@ -2898,6 +2938,11 @@ do_serial_processing_for_unsafe_indexes(LVRelState *vacrel, LVShared *lvshared) lvshared, shared_istat, vacrel); + +#ifdef USE_ASSERT_CHECKING + /* Mark this index has been processed */ + lazy_mark_index_processed(vacrel->indvac_check, idx); +#endif } /* @@ -3837,6 +3882,7 @@ begin_parallel_vacuum(LVRelState *vacrel, BlockNumber nblocks, ParallelContext *pcxt; LVShared *shared; LVDeadTuples *dead_tuples; + LVIndVacCheck *indvac_check = NULL; BufferUsage *buffer_usage; WalUsage *wal_usage; bool *can_parallel_vacuum; @@ -3936,6 +3982,15 @@ begin_parallel_vacuum(LVRelState *vacrel, BlockNumber nblocks, mul_size(sizeof(WalUsage), pcxt->nworkers)); shm_toc_estimate_keys(&pcxt->estimator, 1); + /* + * Estimate size for index check information -- + * PARALLEL_VACUUM_KEY_INDVAC_CHECK. + */ +#ifdef USE_ASSERT_CHECKING + shm_toc_estimate_chunk(&pcxt->estimator, SizeOfLVIndVacCheck(nindexes)); + shm_toc_estimate_keys(&pcxt->estimator, 1); +#endif + /* Finally, estimate PARALLEL_VACUUM_KEY_QUERY_TEXT space */ if (debug_query_string) { @@ -4013,6 +4068,16 @@ begin_parallel_vacuum(LVRelState *vacrel, BlockNumber nblocks, PARALLEL_VACUUM_KEY_QUERY_TEXT, sharedquery); } +#ifdef USE_ASSERT_CHECKING + /* Allocate and initialize space for index vacuum check information */ + indvac_check = shm_toc_allocate(pcxt->toc, SizeOfLVIndVacCheck(nindexes)); + MemSet(indvac_check, 0, SizeOfLVIndVacCheck(nindexes)); + indvac_check->nindexes = nindexes; + shm_toc_insert(pcxt->toc, PARALLEL_VACUUM_KEY_INDVAC_CHECK, indvac_check); +#endif + + vacrel->indvac_check = indvac_check; + pfree(can_parallel_vacuum); return lps; } @@ -4140,6 +4205,7 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc) Relation *indrels; LVShared *lvshared; LVDeadTuples *dead_tuples; + LVIndVacCheck *indvac_check = NULL; BufferUsage *buffer_usage; WalUsage *wal_usage; int nindexes; @@ -4201,6 +4267,10 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc) if (lvshared->maintenance_work_mem_worker > 0) maintenance_work_mem = lvshared->maintenance_work_mem_worker; +#ifdef USE_ASSERT_CHECKING + indvac_check = shm_toc_lookup(toc, PARALLEL_VACUUM_KEY_INDVAC_CHECK, false); +#endif + /* * Initialize vacrel for use as error callback arg by parallel worker. */ @@ -4209,6 +4279,7 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc) vacrel.indname = NULL; vacrel.phase = VACUUM_ERRCB_PHASE_UNKNOWN; /* Not yet processing */ vacrel.dead_tuples = dead_tuples; + vacrel.indvac_check = indvac_check; /* Setup error traceback support for ereport() */ errcallback.callback = vacuum_error_callback; @@ -4331,3 +4402,32 @@ restore_vacuum_error_info(LVRelState *vacrel, vacrel->offnum = saved_vacrel->offnum; vacrel->phase = saved_vacrel->phase; } + +#ifdef USE_ASSERT_CHECKING + +/* Clear information used by lazy_verify_indvac_result() later */ +static void +lazy_clear_indvac_check_info(LVIndVacCheck *indvac_check) +{ + MemSet(indvac_check->processed, false, sizeof(bool) * indvac_check->nindexes); +} + +/* Mark the idx'th index has been processed */ +static void +lazy_mark_index_processed(LVIndVacCheck *indvac_check, int idx) +{ + /* The index must be processed only one process */ + Assert(!indvac_check->processed[idx]); + indvac_check->processed[idx] = true; +} + +/* Check if all indexes have been processed */ +static void +lazy_verify_indvac_result(LVIndVacCheck *indvac_check) +{ + Assert(indvac_check->nindexes > 0); + for (int i = 0; i < indvac_check->nindexes; i++) + Assert(indvac_check->processed[i]); +} + +#endif /* USE_ASSERT_CHECKING */ diff --git a/src/test/regress/expected/vacuum_parallel.out b/src/test/regress/expected/vacuum_parallel.out new file mode 100644 index 0000000000..b2946a22eb --- /dev/null +++ b/src/test/regress/expected/vacuum_parallel.out @@ -0,0 +1,38 @@ +-- +-- VACUUM_PARALLEL +-- All parallel vacuum tests in this file check if any index is not +-- vacuumed during parallel vacuum which causes an assertion failure. +-- +SET max_parallel_maintenance_workers TO 4; +SET min_parallel_index_scan_size TO '64kB'; +CREATE TABLE pvac_test1 (a int) WITH (autovacuum_enabled = off); +INSERT INTO pvac_test1 SELECT generate_series(1, 100000); +CREATE INDEX pvac_large_index_1 ON pvac_test1 (a); +CREATE INDEX pvac_large_index_2 ON pvac_test1 (a); +CREATE INDEX pvac_small_index_1 ON pvac_test1 (a) WHERE a < 10; +CREATE INDEX pvac_small_index_2 ON pvac_test1 (a) WHERE a < 20; +SELECT relname, pg_relation_size(oid) < pg_size_bytes(current_setting('min_parallel_index_scan_size')) as is_small FROM pg_class WHERE relname ~ 'pvac_' AND relkind = 'i' ORDER BY 1; + relname | is_small +--------------------+---------- + pvac_large_index_1 | f + pvac_large_index_2 | f + pvac_small_index_1 | t + pvac_small_index_2 | t +(4 rows) + +DELETE FROM pvac_test1; +-- Do parallel index vacuum. +VACUUM (PARALLEL 4, INDEX_CLEANUP ON) pvac_test1; +-- Do parallel index cleanup. +VACUUM (PARALLEL 4, INDEX_CLEANUP ON) pvac_test1; +CREATE TABLE pvac_test2 (a int, b int4[]) WITH (autovacuum_enabled = off); +INSERT INTO pvac_test2 SELECT g, ARRAY[1, 2, g] FROM generate_series(1, 100000) g; +CREATE INDEX pvac_btree_idx on pvac_test2 USING btree (a); +CREATE INDEX pvac_gin_idx on pvac_test2 USING gin (b); +CREATE INDEX pvac_brin_idx on pvac_test2 USING brin (a); +CREATE INDEX pvac_hash_idx on pvac_test2 USING hash (a); +DELETE FROM pvac_test2; +-- Do parallel index vacuum for different kinds of indexes. +VACUUM (PARALLEL 4, INDEX_CLEANUP ON) pvac_test1; +-- Do parallel index cleanup for different kinds of indexes. +VACUUM (PARALLEL 4, INDEX_CLEANUP ON) pvac_test1; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 7be89178f0..017e962fed 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8 # run by itself so it can run parallel workers test: select_parallel test: write_parallel +test: vacuum_parallel # no relation related tests can be put in this group test: publication subscription diff --git a/src/test/regress/sql/vacuum_parallel.sql b/src/test/regress/sql/vacuum_parallel.sql new file mode 100644 index 0000000000..78a8e3681f --- /dev/null +++ b/src/test/regress/sql/vacuum_parallel.sql @@ -0,0 +1,42 @@ +-- +-- VACUUM_PARALLEL +-- All parallel vacuum tests in this file check if any index is not +-- vacuumed during parallel vacuum which causes an assertion failure. +-- + +SET max_parallel_maintenance_workers TO 4; +SET min_parallel_index_scan_size TO '64kB'; + +CREATE TABLE pvac_test1 (a int) WITH (autovacuum_enabled = off); +INSERT INTO pvac_test1 SELECT generate_series(1, 100000); + +CREATE INDEX pvac_large_index_1 ON pvac_test1 (a); +CREATE INDEX pvac_large_index_2 ON pvac_test1 (a); +CREATE INDEX pvac_small_index_1 ON pvac_test1 (a) WHERE a < 10; +CREATE INDEX pvac_small_index_2 ON pvac_test1 (a) WHERE a < 20; + +SELECT relname, pg_relation_size(oid) < pg_size_bytes(current_setting('min_parallel_index_scan_size')) as is_small FROM pg_class WHERE relname ~ 'pvac_' AND relkind = 'i' ORDER BY 1; + +DELETE FROM pvac_test1; + +-- Do parallel index vacuum. +VACUUM (PARALLEL 4, INDEX_CLEANUP ON) pvac_test1; + +-- Do parallel index cleanup. +VACUUM (PARALLEL 4, INDEX_CLEANUP ON) pvac_test1; + +CREATE TABLE pvac_test2 (a int, b int4[]) WITH (autovacuum_enabled = off); +INSERT INTO pvac_test2 SELECT g, ARRAY[1, 2, g] FROM generate_series(1, 100000) g; + +CREATE INDEX pvac_btree_idx on pvac_test2 USING btree (a); +CREATE INDEX pvac_gin_idx on pvac_test2 USING gin (b); +CREATE INDEX pvac_brin_idx on pvac_test2 USING brin (a); +CREATE INDEX pvac_hash_idx on pvac_test2 USING hash (a); + +DELETE FROM pvac_test2; + +-- Do parallel index vacuum for different kinds of indexes. +VACUUM (PARALLEL 4, INDEX_CLEANUP ON) pvac_test1; + +-- Do parallel index cleanup for different kinds of indexes. +VACUUM (PARALLEL 4, INDEX_CLEANUP ON) pvac_test1;