From 8c1dd4024b8e5ae28b042b99f1a882b022f3bc24 Mon Sep 17 00:00:00 2001 From: Peter Geoghegan 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 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