Commit 962e0cc7 authored by Tom Lane's avatar Tom Lane

Fix race conditions associated with SPGiST redirection tuples.

The correct test for whether a redirection tuple is removable is whether
tuple's xid < RecentGlobalXmin, not OldestXmin; the previous coding
failed to protect index searches being done in concurrent transactions that
have no XID.  This mirrors the recent fix in btree's page recycling logic
made in commit d3abbbeb.

Also, WAL-log the newest XID of any removed redirection tuple on an index
page, and apply ResolveRecoveryConflictWithSnapshot during InHotStandby WAL
replay.  This protects against concurrent Hot Standby transactions possibly
needing to see the redirection tuple(s).

Per my query of 2012-03-12 and subsequent discussion.
parent 7719ed04
...@@ -722,6 +722,7 @@ spgFormDeadTuple(SpGistState *state, int tupstate, ...@@ -722,6 +722,7 @@ spgFormDeadTuple(SpGistState *state, int tupstate,
if (tupstate == SPGIST_REDIRECT) if (tupstate == SPGIST_REDIRECT)
{ {
ItemPointerSet(&tuple->pointer, blkno, offnum); ItemPointerSet(&tuple->pointer, blkno, offnum);
Assert(TransactionIdIsValid(state->myXid));
tuple->xid = state->myXid; tuple->xid = state->myXid;
} }
else else
......
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
#include "storage/bufmgr.h" #include "storage/bufmgr.h"
#include "storage/indexfsm.h" #include "storage/indexfsm.h"
#include "storage/lmgr.h" #include "storage/lmgr.h"
#include "storage/procarray.h"
#include "utils/snapmgr.h" #include "utils/snapmgr.h"
...@@ -49,7 +48,6 @@ typedef struct spgBulkDeleteState ...@@ -49,7 +48,6 @@ typedef struct spgBulkDeleteState
SpGistState spgstate; /* for SPGiST operations that need one */ SpGistState spgstate; /* for SPGiST operations that need one */
spgVacPendingItem *pendingList; /* TIDs we need to (re)visit */ spgVacPendingItem *pendingList; /* TIDs we need to (re)visit */
TransactionId myXmin; /* for detecting newly-added redirects */ TransactionId myXmin; /* for detecting newly-added redirects */
TransactionId OldestXmin; /* for deciding a redirect is obsolete */
BlockNumber lastFilledBlock; /* last non-deletable block */ BlockNumber lastFilledBlock; /* last non-deletable block */
} spgBulkDeleteState; } spgBulkDeleteState;
...@@ -491,8 +489,7 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer) ...@@ -491,8 +489,7 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
* Unlike the routines above, this works on both leaf and inner pages. * Unlike the routines above, this works on both leaf and inner pages.
*/ */
static void static void
vacuumRedirectAndPlaceholder(Relation index, Buffer buffer, vacuumRedirectAndPlaceholder(Relation index, Buffer buffer)
TransactionId OldestXmin)
{ {
Page page = BufferGetPage(buffer); Page page = BufferGetPage(buffer);
SpGistPageOpaque opaque = SpGistPageGetOpaque(page); SpGistPageOpaque opaque = SpGistPageGetOpaque(page);
...@@ -509,6 +506,7 @@ vacuumRedirectAndPlaceholder(Relation index, Buffer buffer, ...@@ -509,6 +506,7 @@ vacuumRedirectAndPlaceholder(Relation index, Buffer buffer,
xlrec.node = index->rd_node; xlrec.node = index->rd_node;
xlrec.blkno = BufferGetBlockNumber(buffer); xlrec.blkno = BufferGetBlockNumber(buffer);
xlrec.nToPlaceholder = 0; xlrec.nToPlaceholder = 0;
xlrec.newestRedirectXid = InvalidTransactionId;
START_CRIT_SECTION(); START_CRIT_SECTION();
...@@ -526,13 +524,18 @@ vacuumRedirectAndPlaceholder(Relation index, Buffer buffer, ...@@ -526,13 +524,18 @@ vacuumRedirectAndPlaceholder(Relation index, Buffer buffer,
dt = (SpGistDeadTuple) PageGetItem(page, PageGetItemId(page, i)); dt = (SpGistDeadTuple) PageGetItem(page, PageGetItemId(page, i));
if (dt->tupstate == SPGIST_REDIRECT && if (dt->tupstate == SPGIST_REDIRECT &&
TransactionIdPrecedes(dt->xid, OldestXmin)) TransactionIdPrecedes(dt->xid, RecentGlobalXmin))
{ {
dt->tupstate = SPGIST_PLACEHOLDER; dt->tupstate = SPGIST_PLACEHOLDER;
Assert(opaque->nRedirection > 0); Assert(opaque->nRedirection > 0);
opaque->nRedirection--; opaque->nRedirection--;
opaque->nPlaceholder++; opaque->nPlaceholder++;
/* remember newest XID among the removed redirects */
if (!TransactionIdIsValid(xlrec.newestRedirectXid) ||
TransactionIdPrecedes(xlrec.newestRedirectXid, dt->xid))
xlrec.newestRedirectXid = dt->xid;
ItemPointerSetInvalid(&dt->pointer); ItemPointerSetInvalid(&dt->pointer);
itemToPlaceholder[xlrec.nToPlaceholder] = i; itemToPlaceholder[xlrec.nToPlaceholder] = i;
...@@ -640,13 +643,13 @@ spgvacuumpage(spgBulkDeleteState *bds, BlockNumber blkno) ...@@ -640,13 +643,13 @@ spgvacuumpage(spgBulkDeleteState *bds, BlockNumber blkno)
else else
{ {
vacuumLeafPage(bds, index, buffer, false); vacuumLeafPage(bds, index, buffer, false);
vacuumRedirectAndPlaceholder(index, buffer, bds->OldestXmin); vacuumRedirectAndPlaceholder(index, buffer);
} }
} }
else else
{ {
/* inner page */ /* inner page */
vacuumRedirectAndPlaceholder(index, buffer, bds->OldestXmin); vacuumRedirectAndPlaceholder(index, buffer);
} }
/* /*
...@@ -723,7 +726,7 @@ spgprocesspending(spgBulkDeleteState *bds) ...@@ -723,7 +726,7 @@ spgprocesspending(spgBulkDeleteState *bds)
/* deal with any deletable tuples */ /* deal with any deletable tuples */
vacuumLeafPage(bds, index, buffer, true); vacuumLeafPage(bds, index, buffer, true);
/* might as well do this while we are here */ /* might as well do this while we are here */
vacuumRedirectAndPlaceholder(index, buffer, bds->OldestXmin); vacuumRedirectAndPlaceholder(index, buffer);
SpGistSetLastUsedPage(index, buffer); SpGistSetLastUsedPage(index, buffer);
...@@ -806,7 +809,6 @@ spgvacuumscan(spgBulkDeleteState *bds) ...@@ -806,7 +809,6 @@ spgvacuumscan(spgBulkDeleteState *bds)
initSpGistState(&bds->spgstate, index); initSpGistState(&bds->spgstate, index);
bds->pendingList = NULL; bds->pendingList = NULL;
bds->myXmin = GetActiveSnapshot()->xmin; bds->myXmin = GetActiveSnapshot()->xmin;
bds->OldestXmin = GetOldestXmin(true, false);
bds->lastFilledBlock = SPGIST_LAST_FIXED_BLKNO; bds->lastFilledBlock = SPGIST_LAST_FIXED_BLKNO;
/* /*
......
...@@ -15,8 +15,9 @@ ...@@ -15,8 +15,9 @@
#include "postgres.h" #include "postgres.h"
#include "access/spgist_private.h" #include "access/spgist_private.h"
#include "access/transam.h"
#include "access/xlogutils.h" #include "access/xlogutils.h"
#include "storage/bufmgr.h" #include "storage/standby.h"
#include "utils/memutils.h" #include "utils/memutils.h"
...@@ -888,6 +889,15 @@ spgRedoVacuumRedirect(XLogRecPtr lsn, XLogRecord *record) ...@@ -888,6 +889,15 @@ spgRedoVacuumRedirect(XLogRecPtr lsn, XLogRecord *record)
ptr += sizeof(spgxlogVacuumRedirect); ptr += sizeof(spgxlogVacuumRedirect);
itemToPlaceholder = (OffsetNumber *) ptr; itemToPlaceholder = (OffsetNumber *) ptr;
/*
* If any redirection tuples are being removed, make sure there are no
* live Hot Standby transactions that might need to see them. This code
* behaves similarly to btree's XLOG_BTREE_REUSE_PAGE case.
*/
if (InHotStandby && TransactionIdIsValid(xldata->newestRedirectXid))
ResolveRecoveryConflictWithSnapshot(xldata->newestRedirectXid,
xldata->node);
if (!(record->xl_info & XLR_BKP_BLOCK_1)) if (!(record->xl_info & XLR_BKP_BLOCK_1))
{ {
buffer = XLogReadBuffer(xldata->node, xldata->blkno, false); buffer = XLogReadBuffer(xldata->node, xldata->blkno, false);
...@@ -1060,8 +1070,9 @@ spg_desc(StringInfo buf, uint8 xl_info, char *rec) ...@@ -1060,8 +1070,9 @@ spg_desc(StringInfo buf, uint8 xl_info, char *rec)
break; break;
case XLOG_SPGIST_VACUUM_REDIRECT: case XLOG_SPGIST_VACUUM_REDIRECT:
out_target(buf, ((spgxlogVacuumRedirect *) rec)->node); out_target(buf, ((spgxlogVacuumRedirect *) rec)->node);
appendStringInfo(buf, "vacuum redirect tuples on page %u", appendStringInfo(buf, "vacuum redirect tuples on page %u, newest XID %u",
((spgxlogVacuumRedirect *) rec)->blkno); ((spgxlogVacuumRedirect *) rec)->blkno,
((spgxlogVacuumRedirect *) rec)->newestRedirectXid);
break; break;
default: default:
appendStringInfo(buf, "unknown spgist op code %u", info); appendStringInfo(buf, "unknown spgist op code %u", info);
......
...@@ -591,6 +591,7 @@ typedef struct spgxlogVacuumRedirect ...@@ -591,6 +591,7 @@ typedef struct spgxlogVacuumRedirect
BlockNumber blkno; /* block number to clean */ BlockNumber blkno; /* block number to clean */
uint16 nToPlaceholder; /* number of redirects to make placeholders */ uint16 nToPlaceholder; /* number of redirects to make placeholders */
OffsetNumber firstPlaceholder; /* first placeholder tuple to remove */ OffsetNumber firstPlaceholder; /* first placeholder tuple to remove */
TransactionId newestRedirectXid; /* newest XID of removed redirects */
/* offsets of redirect tuples to make placeholders follow */ /* offsets of redirect tuples to make placeholders follow */
} spgxlogVacuumRedirect; } spgxlogVacuumRedirect;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment