0001-Use-correct-locking-protocol-during-GIN-posting-tree.patch
application/octet-stream
Filename: 0001-Use-correct-locking-protocol-during-GIN-posting-tree.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch 0001
Subject: Use correct locking protocol during GIN posting tree vacuum
| File | + | − |
|---|---|---|
| src/backend/access/gin/ginbtree.c | 3 | 3 |
| src/backend/access/gin/gindatapage.c | 5 | 5 |
| src/backend/access/gin/ginentrypage.c | 3 | 3 |
| src/backend/access/gin/ginutil.c | 6 | 1 |
| src/backend/access/gin/ginvacuum.c | 130 | 191 |
| src/backend/access/gin/ginxlog.c | 1 | 0 |
| src/backend/access/gin/README | 3 | 14 |
| src/include/access/gin_private.h | 7 | 2 |
| src/include/access/ginxlog.h | 1 | 0 |
From 71e5703357fc99028cfeb8404eae99d98f62b36f Mon Sep 17 00:00:00 2001
From: Andrey <amborodin@acm.org>
Date: Thu, 22 Nov 2018 23:14:46 +0500
Subject: [PATCH] Use correct locking protocol during GIN posting tree vacuum
---
src/backend/access/gin/README | 17 +-
src/backend/access/gin/ginbtree.c | 6 +-
src/backend/access/gin/gindatapage.c | 10 +-
src/backend/access/gin/ginentrypage.c | 6 +-
src/backend/access/gin/ginutil.c | 7 +-
src/backend/access/gin/ginvacuum.c | 321 +++++++++++---------------
src/backend/access/gin/ginxlog.c | 1 +
src/include/access/gin_private.h | 9 +-
src/include/access/ginxlog.h | 1 +
9 files changed, 159 insertions(+), 219 deletions(-)
diff --git a/src/backend/access/gin/README b/src/backend/access/gin/README
index cc434b1feb..947c8355e5 100644
--- a/src/backend/access/gin/README
+++ b/src/backend/access/gin/README
@@ -311,20 +311,9 @@ insert location). So as long as we don't delete the leftmost page on each
level, a search can never follow a downlink to page that's about to be
deleted.
-The previous paragraph's reasoning only applies to searches, and only to
-posting trees. To protect from inserters following a downlink to a deleted
-page, vacuum simply locks out all concurrent insertions to the posting tree,
-by holding a super-exclusive lock on the parent page of subtree with deletable
-pages. Inserters hold a pin on the root page, but searches do not, so while
-new searches cannot begin while root page is locked, any already-in-progress
-scans can continue concurrently with vacuum in corresponding subtree of
-posting tree. To exclude interference with readers vacuum takes exclusive
-locks in a depth-first scan in left-to-right order of page tuples. Leftmost
-page is never deleted. Thus before deleting any page we obtain exclusive
-lock on any left page, effectively excluding deadlock with any reader, despite
-taking parent lock before current and left lock after current. We take left
-lock not for a concurrency reasons, but rather in need to mark page dirty.
-In the entry tree, we never delete pages.
+During vacuum of posting tree leaf pages of posting tree are scanned with
+lock coupling, iterating by rightlink. If leaf if empty, we try to lookup
+parent page for it and delete it.
(This is quite different from the mechanism the btree indexam uses to make
page-deletions safe; it stamps the deleted pages with an XID and keeps the
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index d5a568106c..ec59c342d9 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -271,9 +271,9 @@ ginFindParents(GinBtree btree, GinBtreeStack *stack)
ginFinishSplit(btree, ptr, false, NULL);
}
- leftmostBlkno = btree->getLeftMostChild(btree, page);
+ leftmostBlkno = btree->getLeftMostChild(page);
- while ((offset = btree->findChildPtr(btree, page, stack->blkno, InvalidOffsetNumber)) == InvalidOffsetNumber)
+ while ((offset = btree->findChildPtr(page, stack->blkno, InvalidOffsetNumber)) == InvalidOffsetNumber)
{
blkno = GinPageGetOpaque(page)->rightlink;
if (blkno == InvalidBlockNumber)
@@ -708,7 +708,7 @@ ginFinishSplit(GinBtree btree, GinBtreeStack *stack, bool freestack,
/* move right if it's needed */
page = BufferGetPage(parent->buffer);
- while ((parent->off = btree->findChildPtr(btree, page, stack->blkno, parent->off)) == InvalidOffsetNumber)
+ while ((parent->off = btree->findChildPtr(page, stack->blkno, parent->off)) == InvalidOffsetNumber)
{
if (GinPageRightMost(page))
{
diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
index 9f20513811..529269a2ad 100644
--- a/src/backend/access/gin/gindatapage.c
+++ b/src/backend/access/gin/gindatapage.c
@@ -262,7 +262,7 @@ dataLocateItem(GinBtree btree, GinBtreeStack *stack)
{
stack->off = FirstOffsetNumber;
stack->predictNumber *= GinPageGetOpaque(page)->maxoff;
- return btree->getLeftMostChild(btree, page);
+ return btree->getLeftMostChild(page);
}
low = FirstOffsetNumber;
@@ -312,8 +312,8 @@ dataLocateItem(GinBtree btree, GinBtreeStack *stack)
/*
* Find link to blkno on non-leaf page, returns offset of PostingItem
*/
-static OffsetNumber
-dataFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber storedOff)
+OffsetNumber
+dataFindChildPtr(Page page, BlockNumber blkno, OffsetNumber storedOff)
{
OffsetNumber i,
maxoff = GinPageGetOpaque(page)->maxoff;
@@ -357,8 +357,8 @@ dataFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber stor
/*
* Return blkno of leftmost child
*/
-static BlockNumber
-dataGetLeftMostPage(GinBtree btree, Page page)
+BlockNumber
+dataGetLeftMostPage(Page page)
{
PostingItem *pitem;
diff --git a/src/backend/access/gin/ginentrypage.c b/src/backend/access/gin/ginentrypage.c
index 184cc0af3e..df3868820a 100644
--- a/src/backend/access/gin/ginentrypage.c
+++ b/src/backend/access/gin/ginentrypage.c
@@ -284,7 +284,7 @@ entryLocateEntry(GinBtree btree, GinBtreeStack *stack)
{
stack->off = FirstOffsetNumber;
stack->predictNumber *= PageGetMaxOffsetNumber(page);
- return btree->getLeftMostChild(btree, page);
+ return btree->getLeftMostChild(page);
}
low = FirstOffsetNumber;
@@ -403,7 +403,7 @@ entryLocateLeafEntry(GinBtree btree, GinBtreeStack *stack)
}
static OffsetNumber
-entryFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber storedOff)
+entryFindChildPtr(Page page, BlockNumber blkno, OffsetNumber storedOff)
{
OffsetNumber i,
maxoff = PageGetMaxOffsetNumber(page);
@@ -444,7 +444,7 @@ entryFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber sto
}
static BlockNumber
-entryGetLeftMostPage(GinBtree btree, Page page)
+entryGetLeftMostPage(Page page)
{
IndexTuple itup;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 0a32182dd7..bf354417ee 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -314,7 +314,12 @@ GinNewBuffer(Relation index)
if (PageIsNew(page))
return buffer; /* OK to use, if never initialized */
- if (GinPageIsDeleted(page))
+ /*
+ * We should not reuse page until every transaction started before
+ * deletion is over
+ */
+ if (GinPageIsDeleted(page)
+ && TransactionIdPrecedes(GinPageGetDeleteXid(page), RecentGlobalDataXmin))
return buffer; /* OK to use */
LockBuffer(buffer, GIN_UNLOCK);
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 3104bc12b6..217e214982 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -126,33 +126,14 @@ typedef struct DataPageDeleteStack
* Delete a posting tree page.
*/
static void
-ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkno,
- BlockNumber parentBlkno, OffsetNumber myoff, bool isParentRoot)
+ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, Buffer dBuffer,
+ Buffer lBuffer, Buffer pBuffer, OffsetNumber myoff)
{
- Buffer dBuffer;
- Buffer lBuffer;
- Buffer pBuffer;
Page page,
parentPage;
BlockNumber rightlink;
- /*
- * This function MUST be called only if someone of parent pages hold
- * exclusive cleanup lock. This guarantees that no insertions currently
- * happen in this subtree. Caller also acquire Exclusive lock on deletable
- * page and is acquiring and releasing exclusive lock on left page before.
- * Left page was locked and released. Then parent and this page are
- * locked. We acquire left page lock here only to mark page dirty after
- * changing right pointer.
- */
- lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
- RBM_NORMAL, gvs->strategy);
- dBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, deleteBlkno,
- RBM_NORMAL, gvs->strategy);
- pBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, parentBlkno,
- RBM_NORMAL, gvs->strategy);
-
- LockBuffer(lBuffer, GIN_EXCLUSIVE);
+ /* We epect that buffers are correctly locked */
page = BufferGetPage(dBuffer);
rightlink = GinPageGetOpaque(page)->rightlink;
@@ -169,6 +150,9 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
page = BufferGetPage(lBuffer);
GinPageGetOpaque(page)->rightlink = rightlink;
+ /* for each deletead page we remember last xid which could knew it's address */
+ GinPageSetDeleteXid(page, ReadNewTransactionId());
+
/* Delete downlink from parent */
parentPage = BufferGetPage(pBuffer);
#ifdef USE_ASSERT_CHECKING
@@ -213,6 +197,7 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
data.parentOffset = myoff;
data.rightLink = GinPageGetOpaque(page)->rightlink;
+ data.deleteXid = GinPageGetDeleteXid(page);
XLogRegisterData((char *) &data, sizeof(ginxlogDeletePage));
@@ -222,215 +207,169 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
PageSetLSN(BufferGetPage(lBuffer), recptr);
}
- ReleaseBuffer(pBuffer);
- UnlockReleaseBuffer(lBuffer);
- ReleaseBuffer(dBuffer);
+ UnlockReleaseBuffer(pBuffer);
+ UnlockReleaseBuffer(dBuffer);
END_CRIT_SECTION();
gvs->result->pages_deleted++;
}
-
-/*
- * scans posting tree and deletes empty pages
- */
-static bool
-ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
- DataPageDeleteStack *parent, OffsetNumber myoff)
+/* Descent from root to leftmost leaf page, remembering parent */
+static Buffer
+getPostingTreeLeftmostLeaf(GinVacuumState *gvs,
+ BlockNumber blkno,
+ BlockNumber *leftmost_parent_blkno)
{
- DataPageDeleteStack *me;
- Buffer buffer;
- Page page;
- bool meDelete = false;
- bool isempty;
-
- if (isRoot)
- {
- me = parent;
- }
- else
+ *leftmost_parent_blkno = InvalidBlockNumber;
+ /* Probably, we can have sanity check here? */
+ while (true)
{
- if (!parent->child)
- {
- me = (DataPageDeleteStack *) palloc0(sizeof(DataPageDeleteStack));
- me->parent = parent;
- parent->child = me;
- me->leftBlkno = InvalidBlockNumber;
- }
- else
- me = parent->child;
- }
-
- buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
- RBM_NORMAL, gvs->strategy);
-
- if (!isRoot)
- LockBuffer(buffer, GIN_EXCLUSIVE);
-
- page = BufferGetPage(buffer);
-
- Assert(GinPageIsData(page));
-
- if (!GinPageIsLeaf(page))
- {
- OffsetNumber i;
+ Buffer buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM,
+ blkno,
+ RBM_NORMAL, gvs->strategy);
+ Page page = BufferGetPage(buffer);
+ LockBuffer(buffer, GIN_SHARE);
- me->blkno = blkno;
- for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
+ if (GinPageIsLeaf(page))
{
- PostingItem *pitem = GinDataPageGetPostingItem(page, i);
-
- if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), false, me, i))
- i--;
+ LockBuffer(buffer, GIN_UNLOCK);
+ LockBufferForCleanup(buffer);
+ /* Root can become internal page, recheck */
+ if (GinPageIsLeaf(page))
+ {
+ return buffer;
+ }
}
- }
- if (GinPageIsLeaf(page))
- isempty = GinDataLeafPageIsEmpty(page);
- else
- isempty = GinPageGetOpaque(page)->maxoff < FirstOffsetNumber;
+ *leftmost_parent_blkno = blkno;
+ blkno = dataGetLeftMostPage(page);
+ UnlockReleaseBuffer(buffer);
+ }
+}
- if (isempty)
+/*
+ * Find parent for leaf page using information about previously known parent
+ * Returned buffer must be exclusively locked
+ */
+static Buffer tryFindParentForLeaf(GinVacuumState *gvs,
+ BlockNumber *parent_cache,
+ BlockNumber leaf_blkno,
+ OffsetNumber *myoff)
+{
+ Buffer buffer;
+ Page page;
+ OffsetNumber offnum;
+ /* Potentially we scan through all lowest-level internal pages */
+ while (true)
{
- /* we never delete the left- or rightmost branch */
- if (me->leftBlkno != InvalidBlockNumber && !GinPageRightMost(page))
+ /* we have nowhere to search anymore */
+ if (!BlockNumberIsValid(*parent_cache))
+ return InvalidBuffer;
+
+ buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM,
+ *parent_cache,
+ RBM_NORMAL, gvs->strategy);
+ page = BufferGetPage(buffer);
+ LockBuffer(buffer, GIN_EXCLUSIVE);
+ Assert(!GinPageIsLeaf(page));
+
+ /* try to find downlink to deletable page */
+ offnum = dataFindChildPtr(page, leaf_blkno, InvalidOffsetNumber);
+ if (OffsetNumberIsValid(offnum))
{
- Assert(!isRoot);
- ginDeletePage(gvs, blkno, me->leftBlkno, me->parent->blkno, myoff, me->parent->isRoot);
- meDelete = true;
+ /*
+ * we should not delete highest keys on page
+ * posting tree uses them for concurrency reasons
+ * instead of highkeys
+ */
+ if (offnum == PageGetMaxOffsetNumber(page))
+ {
+ UnlockReleaseBuffer(buffer);
+ return InvalidBuffer;
+ }
+ /* OK, return it */
+ *myoff = offnum;
+ return buffer;
}
+ /*
+ * downlink to deletable page not found - try next
+ * in case of races vacuum will not be able to delete leaves,
+ * but this is acceptable
+ */
+ *parent_cache = GinPageGetOpaque(page)->rightlink;
+ UnlockReleaseBuffer(buffer);
}
-
- if (!isRoot)
- LockBuffer(buffer, GIN_UNLOCK);
-
- ReleaseBuffer(buffer);
-
- if (!meDelete)
- me->leftBlkno = blkno;
-
- return meDelete;
}
-
-/*
- * Scan through posting tree, delete empty tuples from leaf pages.
- * Also, this function collects empty subtrees (with all empty leafs).
- * For parents of these subtrees CleanUp lock is taken, then we call
- * ScanToDelete. This is done for every inner page, which points to
- * empty subtree.
- */
-static bool
-ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno, bool isRoot)
+static void
+ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
{
- Buffer buffer;
- Page page;
- bool hasVoidPage = false;
+ BlockNumber parentBlkno;
+ Buffer buffer, prevBuffer;
+ Page page, prevPage;
MemoryContext oldCxt;
- buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
- RBM_NORMAL, gvs->strategy);
- page = BufferGetPage(buffer);
+ /*
+ * Find leftmost page, then move right with lock coupling to guarantee that all
+ * tuples are removed.
+ */
+ prevBuffer = getPostingTreeLeftmostLeaf(gvs, rootBlkno, &parentBlkno);
+ prevPage = BufferGetPage(prevBuffer);
- ginTraverseLock(buffer, false);
+ Assert(GinPageIsLeaf(prevPage));
- Assert(GinPageIsData(page));
+ oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
+ /* leftmost page is undeletable, just clean it */
+ ginVacuumPostingTreeLeaf(gvs->index, prevBuffer, gvs);
+ MemoryContextSwitchTo(oldCxt);
+ MemoryContextReset(gvs->tmpCxt);
- if (GinPageIsLeaf(page))
+ while (!GinPageRightMost(prevPage))
{
+ BlockNumber blkno = GinPageGetOpaque(prevPage)->rightlink;
+ buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM,
+ blkno,
+ RBM_NORMAL, gvs->strategy);
+ page = BufferGetPage(buffer);
+ LockBufferForCleanup(buffer);
+
+ /* Cleanup tuples on page */
oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
- ginVacuumPostingTreeLeaf(gvs->index, buffer, gvs);
+ ginVacuumPostingTreeLeaf(gvs->index, prevBuffer, gvs);
MemoryContextSwitchTo(oldCxt);
MemoryContextReset(gvs->tmpCxt);
- /* if root is a leaf page, we don't desire further processing */
- if (GinDataLeafPageIsEmpty(page))
- hasVoidPage = true;
-
- UnlockReleaseBuffer(buffer);
-
- return hasVoidPage;
- }
- else
- {
- OffsetNumber i;
- bool hasEmptyChild = false;
- bool hasNonEmptyChild = false;
- OffsetNumber maxoff = GinPageGetOpaque(page)->maxoff;
- BlockNumber *children = palloc(sizeof(BlockNumber) * (maxoff + 1));
-
- /*
- * Read all children BlockNumbers. Not sure it is safe if there are
- * many concurrent vacuums.
- */
-
- for (i = FirstOffsetNumber; i <= maxoff; i++)
+ if (!GinPageRightMost(page)
+ && GinDataLeafPageIsEmpty(page)
+ && BlockNumberIsValid(parentBlkno))
{
- PostingItem *pitem = GinDataPageGetPostingItem(page, i);
-
- children[i] = PostingItemGetBlockNumber(pitem);
- }
+ /* try to delete this page */
+ OffsetNumber myoff;
+ Buffer parentBuffer = tryFindParentForLeaf(gvs, &parentBlkno, blkno, &myoff);
+ if (BufferIsValid(parentBuffer))
+ {
+ /* All pages are properly locked */
+ ginDeletePage(gvs, blkno, buffer, prevBuffer, parentBuffer, myoff);
- UnlockReleaseBuffer(buffer);
+ /* prevPage now has new rightlink, be careful not to release prevPage */
- for (i = FirstOffsetNumber; i <= maxoff; i++)
- {
- if (ginVacuumPostingTreeLeaves(gvs, children[i], false))
- hasEmptyChild = true;
- else
- hasNonEmptyChild = true;
+ continue;
+ }
}
+ UnlockReleaseBuffer(prevBuffer);
- pfree(children);
-
- vacuum_delay_point();
+ prevBuffer = buffer;
+ prevPage = BufferGetPage(prevBuffer);
/*
- * All subtree is empty - just return true to indicate that parent
- * must do a cleanup, unless we are ROOT and there is way to go upper.
+ * During delaypoint we hold one cleanup lock. We cannot avoid this lock
+ * to guaranty that all tuples were vacuumed.
*/
-
- if (hasEmptyChild && !hasNonEmptyChild && !isRoot)
- return true;
-
- if (hasEmptyChild)
- {
- DataPageDeleteStack root,
- *ptr,
- *tmp;
-
- buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
- RBM_NORMAL, gvs->strategy);
- LockBufferForCleanup(buffer);
-
- memset(&root, 0, sizeof(DataPageDeleteStack));
- root.leftBlkno = InvalidBlockNumber;
- root.isRoot = true;
-
- ginScanToDelete(gvs, blkno, true, &root, InvalidOffsetNumber);
-
- ptr = root.child;
-
- while (ptr)
- {
- tmp = ptr->child;
- pfree(ptr);
- ptr = tmp;
- }
-
- UnlockReleaseBuffer(buffer);
- }
-
- /* Here we have deleted all empty subtrees */
- return false;
+ vacuum_delay_point();
}
-}
-static void
-ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
-{
- ginVacuumPostingTreeLeaves(gvs, rootBlkno, true);
+ UnlockReleaseBuffer(prevBuffer);
}
/*
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index 7701a2d6bf..7821f69077 100644
--- a/src/backend/access/gin/ginxlog.c
+++ b/src/backend/access/gin/ginxlog.c
@@ -518,6 +518,7 @@ ginRedoDeletePage(XLogReaderState *record)
page = BufferGetPage(dbuffer);
Assert(GinPageIsData(page));
GinPageGetOpaque(page)->flags = GIN_DELETED;
+ GinPageSetDeleteXid(page, data->deleteXid);
PageSetLSN(page, lsn);
MarkBufferDirty(dbuffer);
}
diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h
index 81bf8734ce..f360196af4 100644
--- a/src/include/access/gin_private.h
+++ b/src/include/access/gin_private.h
@@ -44,6 +44,9 @@ typedef struct GinOptions
#define GIN_SHARE BUFFER_LOCK_SHARE
#define GIN_EXCLUSIVE BUFFER_LOCK_EXCLUSIVE
+#define GinPageGetDeleteXid(page) ( ((PageHeader) (page))->pd_prune_xid )
+#define GinPageSetDeleteXid(page, val) ( ((PageHeader) (page))->pd_prune_xid = val)
+
/*
* GinState: working data structure describing the index being worked on
@@ -144,12 +147,12 @@ typedef struct GinBtreeData
{
/* search methods */
BlockNumber (*findChildPage) (GinBtree, GinBtreeStack *);
- BlockNumber (*getLeftMostChild) (GinBtree, Page);
+ BlockNumber (*getLeftMostChild) (Page);
bool (*isMoveRight) (GinBtree, Page);
bool (*findItem) (GinBtree, GinBtreeStack *);
/* insert methods */
- OffsetNumber (*findChildPtr) (GinBtree, Page, BlockNumber, OffsetNumber);
+ OffsetNumber (*findChildPtr) (Page, BlockNumber, OffsetNumber);
GinPlaceToPageRC (*beginPlaceToPage) (GinBtree, Buffer, GinBtreeStack *, void *, BlockNumber, void **, Page *, Page *);
void (*execPlaceToPage) (GinBtree, Buffer, GinBtreeStack *, void *, BlockNumber, void *);
void *(*prepareDownlink) (GinBtree, Buffer);
@@ -225,6 +228,8 @@ extern void ginInsertItemPointers(Relation index, BlockNumber rootBlkno,
GinStatsData *buildStats);
extern GinBtreeStack *ginScanBeginPostingTree(GinBtree btree, Relation index, BlockNumber rootBlkno, Snapshot snapshot);
extern void ginDataFillRoot(GinBtree btree, Page root, BlockNumber lblkno, Page lpage, BlockNumber rblkno, Page rpage);
+extern BlockNumber dataGetLeftMostPage(Page page);
+extern OffsetNumber dataFindChildPtr(Page page, BlockNumber blkno, OffsetNumber storedOff);
/*
* This is declared in ginvacuum.c, but is passed between ginVacuumItemPointers
diff --git a/src/include/access/ginxlog.h b/src/include/access/ginxlog.h
index 64a3c9e18b..d7f44d6b83 100644
--- a/src/include/access/ginxlog.h
+++ b/src/include/access/ginxlog.h
@@ -158,6 +158,7 @@ typedef struct ginxlogDeletePage
{
OffsetNumber parentOffset;
BlockNumber rightLink;
+ TransactionId deleteXid; /* last Xid which could see page in scan */
} ginxlogDeletePage;
#define XLOG_GIN_UPDATE_META_PAGE 0x60
--
2.17.2 (Apple Git-113)