diff options
| author | Peter Geoghegan | 2026-08-19 17:46:34 +0000 |
|---|---|---|
| committer | Peter Geoghegan | 2026-08-19 17:46:34 +0000 |
| commit | 0bace7af3dff862e9643e45df11449857cc56932 (patch) | |
| tree | 77531aec1a740c7a50ee2a8906c317da083a9568 | |
| parent | fbf889de95606471cecf4ff5533dc24d40cb8700 (diff) | |
Fix GIN multiple-VACUUM-scans pending list bug.
ginbulkdelete performs pending list cleanup before it searches the entry
tree (and any posting trees) for dead TIDs. This is necessary to avoid
leaving behind dangling TID references that index vacuuming is required
to remove; nothing prevents recently inserted pending list tuples from
containing TIDs that VACUUM already considers dead.
However, ginbulkdelete neglected to perform pending list cleanup on
VACUUM's second or subsequent call. It was therefore possible for a
VACUUM that requires multiple rounds of index vacuuming to leave behind
dangling references.
To fix, teach ginbulkdelete to perform pending list cleanup during every
call. In passing, tweak some related comments in the pending list
cleanup path to make it clear why it's safe for VACUUM to not _fully_
empty an index's pending list.
This was arguably an oversight in commit e2c79e14, which fixed a similar
issue where pending list cleanup by VACUUM could end early, but missed
this closely related problem.
Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru>
Discussion: https://postgr.es/m/CAH2-Wzmsa-RPA2Ko8A5LaGOnmbpimJ--71xkiBqwgjk3Fq8YEg@mail.gmail.com
Backpatch-through: 14
| -rw-r--r-- | src/backend/access/gin/ginfast.c | 15 | ||||
| -rw-r--r-- | src/backend/access/gin/ginvacuum.c | 18 | ||||
| -rw-r--r-- | src/include/access/gin_private.h | 2 |
3 files changed, 22 insertions, 13 deletions
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c index 3640977290a..761bff70807 100644 --- a/src/backend/access/gin/ginfast.c +++ b/src/backend/access/gin/ginfast.c @@ -779,7 +779,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) { @@ -810,7 +810,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 = @@ -882,11 +884,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 move 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 6665ce0fec7..f0f253e9755 100644 --- a/src/backend/access/gin/ginvacuum.c +++ b/src/backend/access/gin/ginvacuum.c @@ -603,14 +603,20 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, { /* Yes, so initialize stats to zeroes */ stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); - - /* - * and cleanup any pending inserts - */ - ginInsertCleanup(&gvs.ginstate, !IsAutoVacuumWorkerProcess(), - 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, !IsAutoVacuumWorkerProcess(), + false, true, stats); + /* we'll re-count the tuples each time */ stats->num_index_tuples = 0; gvs.result = stats; diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h index 670a40b4bee..53282bb8ba5 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -462,7 +462,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 */ |
