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;
