diff options
| author | Peter Geoghegan | 2026-08-19 19:45:46 +0000 |
|---|---|---|
| committer | Peter Geoghegan | 2026-08-19 19:45:46 +0000 |
| commit | 5ce645173e6d4f90bccce1373f54879665166b6c (patch) | |
| tree | 057f1c1e60560f844fc56c5ca85b087b60f909e6 | |
| parent | 76c05f5080c8c0b285247e58f7c2303a647646e6 (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.c | 4 | ||||
| -rw-r--r-- | src/backend/access/gist/gistscan.c | 8 |
2 files changed, 12 insertions, 0 deletions
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c index adbf622c83c..b87c43b98d0 100644 --- a/src/backend/access/gist/gistget.c +++ b/src/backend/access/gist/gistget.c @@ -425,7 +425,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); @@ -722,6 +725,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 4252ff44b25..d8a44c43b4a 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. |
