summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Geoghegan2026-08-19 19:45:50 +0000
committerPeter Geoghegan2026-08-19 19:45:50 +0000
commitd316899e1436e4d0268f596f405f9cdeb9cbc1b9 (patch)
tree05e9c5258897fb40d169a1ce5b5249b97a1f826d
parentd778f00c145d94285e77587f00a44dcf99cc0620 (diff)
GiST: Invalidate killed items consistently.
GiST neglected to invalidate its killedItems[] array on a rescan. As a result, it was just about possible for the wrong tuples from the wrong index page to be LP_DEAD-marked on a rescan. The scan mistakenly believed that the previous rescan's killedItems[] were for this rescan's curBlkno, causing index corruption. To fix, bring GiST in line with nbtree and hash: call gistkillitems from both gistrescan and gistendscan (the existing gistgettuple caller still handles the common case where we need to LP_DEAD-mark before moving on to the next page). That way the scan's pending killedItems[] are passed to gistkillitems while they still describe items from curBlkno. When gistkillitems runs, it'll invalidate the array in passing (and won't needlessly miss out on an opportunity to LP_DEAD-mark eligible index tuples). Back branches just get minimal hardening: we invalidate killedItems[] at the places where the master branch gets new calls to gistkillitems (and we invalidate curBlkno and curPageLSN on a rescan). The test that proved corruption on master didn't result in corruption on any stable branch, though only because, without commit 9c9ddf109, we'd clobber curPageLSN without also updating curBlkno -- which accidentally prevented it. Relying on gistkillitems to not LP_DEAD-mark by passing it a curBlkno whose curPageLSN was taken from an entirely different page seems like a very bad idea, which is why this issue is being treated as a bug affecting all stable branches. Author: Peter Geoghegan <pg@bowt.ie> Reviewed-By: Andrey Borodin <x4mmm@yandex-team.ru> Discussion: https://postgr.es/m/CAH2-WzmwEThnQf17Ju+t0N9_KJLsEQSXzYrFnaS2=s4KnGGrqw@mail.gmail.com Backpatch-through: 14
-rw-r--r--src/backend/access/gist/gistget.c4
-rw-r--r--src/backend/access/gist/gistscan.c8
2 files changed, 12 insertions, 0 deletions
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index b35b8a97577..634c9fc6900 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -423,7 +423,10 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
* killed tuple as not passing the qual.
*/
if (scan->ignore_killed_tuples && ItemIdIsDead(iid))
+ {
+ Assert(GistPageIsLeaf(page));
continue;
+ }
it = (IndexTuple) PageGetItem(page, iid);
@@ -720,6 +723,7 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir)
CHECK_FOR_INTERRUPTS();
/* save current item BlockNumber for next gistkillitems() call */
+ Assert(so->numKilled == 0);
so->curBlkno = item->blkno;
/*
diff --git a/src/backend/access/gist/gistscan.c b/src/backend/access/gist/gistscan.c
index e05801e2f5b..f5e070f6b98 100644
--- a/src/backend/access/gist/gistscan.c
+++ b/src/backend/access/gist/gistscan.c
@@ -133,7 +133,12 @@ gistrescan(IndexScanDesc scan, ScanKey key, int nkeys,
int i;
MemoryContext oldCxt;
+ /* invalidate any killed items still pending */
+ so->numKilled = 0;
+
/* rescan an existing indexscan --- reset state */
+ so->curBlkno = InvalidBlockNumber;
+ so->curPageLSN = InvalidXLogRecPtr;
/*
* The first time through, we create the search queue in the scanCxt.
@@ -350,6 +355,9 @@ gistendscan(IndexScanDesc scan)
{
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
+ /* invalidate any killed items still pending */
+ so->numKilled = 0;
+
/*
* freeGISTstate is enough to clean up everything made by gistbeginscan,
* as well as the queueCxt if there is a separate context for it.