From 8c1dd4024b8e5ae28b042b99f1a882b022f3bc24 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Tue, 18 Aug 2026 23:12:44 -0400
Subject: [PATCH v1 2/2] Clean up the GIN pending list on every ginbulkdelete()
 call.

Author: Peter Geoghegan <pg@bowt.ie>
Discussion: https://postgr.es/m/CAH2-Wzmsa-RPA2Ko8A5LaGOnmbpimJ--71xkiBqwgjk3Fq8YEg@mail.gmail.com
Backpatch-through: 14
---
 src/include/access/gin_private.h   |  2 +-
 src/backend/access/gin/ginfast.c   | 15 +++++++++------
 src/backend/access/gin/ginvacuum.c | 18 ++++++++++++------
 3 files changed, 22 insertions(+), 13 deletions(-)

diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h
index 6725ee283..3c5fd6ba8 100644
--- a/src/include/access/gin_private.h
+++ b/src/include/access/gin_private.h
@@ -468,7 +468,7 @@ extern void ginHeapTupleFastCollect(GinState *ginstate,
 									GinTupleCollector *collector,
 									OffsetNumber attnum, Datum value, bool isNull,
 									ItemPointer ht_ctid);
-extern void ginInsertCleanup(GinState *ginstate, bool full_clean,
+extern void ginInsertCleanup(GinState *ginstate, bool must_empty_list,
 							 bool fill_fsm, bool forceCleanup, IndexBulkDeleteResult *stats);
 
 /* ginpostinglist.c */
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index f50848eb6..74dd4ccd0 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -777,7 +777,7 @@ processPendingPage(BuildAccumulator *accum, KeyArray *ka,
  * If stats isn't null, we count deleted pending pages into the counts.
  */
 void
-ginInsertCleanup(GinState *ginstate, bool full_clean,
+ginInsertCleanup(GinState *ginstate, bool must_empty_list,
 				 bool fill_fsm, bool forceCleanup,
 				 IndexBulkDeleteResult *stats)
 {
@@ -808,7 +808,9 @@ ginInsertCleanup(GinState *ginstate, bool full_clean,
 	{
 		/*
 		 * We are called from [auto]vacuum/analyze or gin_clean_pending_list()
-		 * and we would like to wait concurrent cleanup to finish.
+		 * and we must wait for concurrent cleanup to finish.  In particular,
+		 * VACUUM must have the opportunity to remove any dead TIDs that are
+		 * now in the pending list.
 		 */
 		LockPage(index, GIN_METAPAGE_BLKNO, ExclusiveLock);
 		workMemory =
@@ -880,11 +882,12 @@ ginInsertCleanup(GinState *ginstate, bool full_clean,
 
 		/*
 		 * Are we walk through the page which as we remember was a tail when
-		 * we start our cleanup?  But if caller asks us to clean up whole
-		 * pending list then ignore old tail, we will work until list becomes
-		 * empty.
+		 * we start our cleanup?  But if caller asks us to fully empty the
+		 * pending list (not just remove all items that were in the list when
+		 * blknoFinish was established) then ignore old tail and work until
+		 * the list is fully empty.
 		 */
-		if (blkno == blknoFinish && full_clean == false)
+		if (blkno == blknoFinish && !must_empty_list)
 			cleanupFinish = true;
 
 		/*
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 292b26cbf..d69d59748 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -640,14 +640,20 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	{
 		/* Yes, so initialize stats to zeroes */
 		stats = palloc0_object(IndexBulkDeleteResult);
-
-		/*
-		 * and cleanup any pending inserts
-		 */
-		ginInsertCleanup(&gvs.ginstate, !AmAutoVacuumWorkerProcess(),
-						 false, true, stats);
 	}
 
+	/*
+	 * The pending list might have already-dead TIDs that VACUUM now requires
+	 * us to remove from the index.  We must force cleanup of the pending list
+	 * now, before vacuuming proper begins, to make sure nothing is missed.
+	 *
+	 * When running in an autovacuum worker, we won't necessarily _fully_
+	 * empty the pending list.  This is still safe; concurrent inserters
+	 * cannot insert new tuples whose TIDs VACUUM needs us to remove.
+	 */
+	ginInsertCleanup(&gvs.ginstate, !AmAutoVacuumWorkerProcess(),
+					 false, true, stats);
+
 	/* we'll re-count the tuples each time */
 	stats->num_index_tuples = 0;
 	gvs.result = stats;
-- 
2.53.0

