The vacuum callers of ginInsertCleanup weren't able to pass the current BufferAccessStrategy, since the function didn't take a strategy argument. I modified the function to take a strategy, and passed it from the vacuum down to ReadBufferExtended. Other call-sites pass NULL as a strategy, which preserves the current behavior.
I did notice 6c228755add, which uses ReadStream in ginvacuumcleanup()'s full-index scan, but I don't think that will work here: that scan knows the full block range, but the pending-list needs to read each page to get the next block. Running a quick test (script attached), we do see the strategy changing how GIN treats the buffer cache. I originally noticed this as just an inconsistency, but it may be a good improvement to how GIN vacuum interacts with shared buffers (fewer evictions on other processes). - Kevin Rocker
From 187e72ff3c69ffb0a17f496b4eb3add64958cdbb Mon Sep 17 00:00:00 2001 From: Kevin Rocker <[email protected]> Date: Fri, 31 Jul 2026 23:34:10 +0200 Subject: [PATCH v1] Use the vacuum buffer access strategy in GIN pending-list cleanup. When ginInsertCleanup() is reached from VACUUM, it reads the metapage and every pending-list page with ReadBuffer(), ignoring BufferAccessStrategy from the vacuum machinery. The same was true of shiftList(), which re-reads the processed pages before deleting them. Pass the strategy down from the three vacuum-side callers. The post-insert cleanup path and gin_clean_pending_list() pass NULL, preserving current behavior. --- src/backend/access/gin/ginfast.c | 29 ++++++++++++++++++++--------- src/backend/access/gin/ginvacuum.c | 7 ++++--- src/include/access/gin_private.h | 4 +++- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c index f50848eb65a..6fb3180c4f8 100644 --- a/src/backend/access/gin/ginfast.c +++ b/src/backend/access/gin/ginfast.c @@ -468,7 +468,7 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector) * pending list not forcibly. */ if (needCleanup) - ginInsertCleanup(ginstate, false, true, false, NULL); + ginInsertCleanup(ginstate, false, true, false, NULL, NULL); } /* @@ -552,7 +552,8 @@ ginHeapTupleFastCollect(GinState *ginstate, */ static void shiftList(Relation index, Buffer metabuffer, BlockNumber newHead, - bool fill_fsm, IndexBulkDeleteResult *stats) + bool fill_fsm, IndexBulkDeleteResult *stats, + BufferAccessStrategy strategy) { Page metapage; GinMetaPageData *metadata; @@ -575,7 +576,9 @@ shiftList(Relation index, Buffer metabuffer, BlockNumber newHead, while (data.ndeleted < GIN_NDELETE_AT_ONCE && blknoToDelete != newHead) { freespace[data.ndeleted] = blknoToDelete; - buffers[data.ndeleted] = ReadBuffer(index, blknoToDelete); + buffers[data.ndeleted] = ReadBufferExtended(index, MAIN_FORKNUM, + blknoToDelete, + RBM_NORMAL, strategy); LockBuffer(buffers[data.ndeleted], GIN_EXCLUSIVE); page = BufferGetPage(buffers[data.ndeleted]); @@ -775,11 +778,16 @@ processPendingPage(BuildAccumulator *accum, KeyArray *ka, * FSM. * * If stats isn't null, we count deleted pending pages into the counts. + * + * If strategy isn't null, use that buffer access strategy to read the + * pending-list pages; vacuum passes its strategy so that the cleanup + * doesn't disturb the shared buffer cache more than necessary. */ void ginInsertCleanup(GinState *ginstate, bool full_clean, bool fill_fsm, bool forceCleanup, - IndexBulkDeleteResult *stats) + IndexBulkDeleteResult *stats, + BufferAccessStrategy strategy) { Relation index = ginstate->index; Buffer metabuffer, @@ -827,7 +835,8 @@ ginInsertCleanup(GinState *ginstate, bool full_clean, workMemory = work_mem; } - metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO); + metabuffer = ReadBufferExtended(index, MAIN_FORKNUM, GIN_METAPAGE_BLKNO, + RBM_NORMAL, strategy); LockBuffer(metabuffer, GIN_SHARE); metapage = BufferGetPage(metabuffer); metadata = GinPageGetMeta(metapage); @@ -850,7 +859,8 @@ ginInsertCleanup(GinState *ginstate, bool full_clean, * Read and lock head of pending list */ blkno = metadata->head; - buffer = ReadBuffer(index, blkno); + buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno, + RBM_NORMAL, strategy); LockBuffer(buffer, GIN_SHARE); page = BufferGetPage(buffer); @@ -971,7 +981,7 @@ ginInsertCleanup(GinState *ginstate, bool full_clean, * remove read pages from pending list, at this point all content * of read pages is in regular structure */ - shiftList(index, metabuffer, blkno, fill_fsm, stats); + shiftList(index, metabuffer, blkno, fill_fsm, stats, strategy); /* At this point, some pending pages have been freed up */ fsm_vac = true; @@ -1003,7 +1013,8 @@ ginInsertCleanup(GinState *ginstate, bool full_clean, * Read next page in pending list */ vacuum_delay_point(false); - buffer = ReadBuffer(index, blkno); + buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno, + RBM_NORMAL, strategy); LockBuffer(buffer, GIN_SHARE); page = BufferGetPage(buffer); } @@ -1077,7 +1088,7 @@ gin_clean_pending_list(PG_FUNCTION_ARGS) GinState ginstate; initGinState(&ginstate, indexRel); - ginInsertCleanup(&ginstate, true, true, true, &stats); + ginInsertCleanup(&ginstate, true, true, true, &stats, NULL); } else ereport(DEBUG1, diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c index 040f21a92e3..e8baf419674 100644 --- a/src/backend/access/gin/ginvacuum.c +++ b/src/backend/access/gin/ginvacuum.c @@ -635,7 +635,7 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, * and cleanup any pending inserts */ ginInsertCleanup(&gvs.ginstate, !AmAutoVacuumWorkerProcess(), - false, true, stats); + false, true, stats, info->strategy); } /* we'll re-count the tuples each time */ @@ -750,7 +750,8 @@ ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) if (AmAutoVacuumWorkerProcess()) { initGinState(&ginstate, index); - ginInsertCleanup(&ginstate, false, true, true, stats); + ginInsertCleanup(&ginstate, false, true, true, stats, + info->strategy); } return stats; } @@ -764,7 +765,7 @@ ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) stats = palloc0_object(IndexBulkDeleteResult); initGinState(&ginstate, index); ginInsertCleanup(&ginstate, !AmAutoVacuumWorkerProcess(), - false, true, stats); + false, true, stats, info->strategy); } memset(&idxStat, 0, sizeof(idxStat)); diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h index 6725ee2839f..7319242cd16 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -469,7 +469,9 @@ extern void ginHeapTupleFastCollect(GinState *ginstate, OffsetNumber attnum, Datum value, bool isNull, ItemPointer ht_ctid); extern void ginInsertCleanup(GinState *ginstate, bool full_clean, - bool fill_fsm, bool forceCleanup, IndexBulkDeleteResult *stats); + bool fill_fsm, bool forceCleanup, + IndexBulkDeleteResult *stats, + BufferAccessStrategy strategy); /* ginpostinglist.c */ -- 2.54.0
gin-pending-strategy-test.sql
Description: application/sql
