mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
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
This commit is contained in:
@@ -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;
|
||||
|
||||
/*
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user