Example #1
0
static bool
ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno, bool isRoot, Buffer *rootBuffer)
{
	Buffer		buffer;
	Page		page;
	bool		hasVoidPage = FALSE;
	MemoryContext oldCxt;

	buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
								RBM_NORMAL, gvs->strategy);
	page = BufferGetPage(buffer);

	/*
	 * We should be sure that we don't concurrent with inserts, insert process
	 * never release root page until end (but it can unlock it and lock
	 * again). New scan can't start but previously started ones work
	 * concurrently.
	 */
	if (isRoot)
		LockBufferForCleanup(buffer);
	else
		LockBuffer(buffer, GIN_EXCLUSIVE);

	Assert(GinPageIsData(page));

	if (GinPageIsLeaf(page))
	{
		oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
		ginVacuumPostingTreeLeaf(gvs->index, buffer, gvs);
		MemoryContextSwitchTo(oldCxt);
		MemoryContextReset(gvs->tmpCxt);

		/* if root is a leaf page, we don't desire further processing */
		if (!isRoot && !hasVoidPage && GinDataLeafPageIsEmpty(page))
			hasVoidPage = TRUE;
	}
	else
	{
		OffsetNumber i;
		bool		isChildHasVoid = FALSE;

		for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
		{
			PostingItem *pitem = GinDataPageGetPostingItem(page, i);

			if (ginVacuumPostingTreeLeaves(gvs, PostingItemGetBlockNumber(pitem), FALSE, NULL))
				isChildHasVoid = TRUE;
		}

		if (isChildHasVoid)
			hasVoidPage = TRUE;
	}

	/*
	 * if we have root and there are empty pages in tree, then we don't
	 * release lock to go further processing and guarantee that tree is unused
	 */
	if (!(isRoot && hasVoidPage))
	{
		UnlockReleaseBuffer(buffer);
	}
	else
	{
		Assert(rootBuffer);
		*rootBuffer = buffer;
	}

	return hasVoidPage;
}
Example #2
0
/*
 * scans posting tree and deletes empty pages
 */
static bool
ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
				DataPageDeleteStack *parent, OffsetNumber myoff)
{
	DataPageDeleteStack *me;
	Buffer		buffer;
	Page		page;
	bool		meDelete = FALSE;
	bool		isempty;

	if (isRoot)
	{
		me = parent;
	}
	else
	{
		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);
	page = BufferGetPage(buffer);

	Assert(GinPageIsData(page));

	if (!GinPageIsLeaf(page))
	{
		OffsetNumber i;

		me->blkno = blkno;
		for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
		{
			PostingItem *pitem = GinDataPageGetPostingItem(page, i);

			if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), FALSE, me, i))
				i--;
		}
	}

	if (GinPageIsLeaf(page))
		isempty = GinDataLeafPageIsEmpty(page);
	else
		isempty = GinPageGetOpaque(page)->maxoff < FirstOffsetNumber;

	if (isempty)
	{
		/* we never delete the left- or rightmost branch */
		if (me->leftBlkno != InvalidBlockNumber && !GinPageRightMost(page))
		{
			Assert(!isRoot);
			ginDeletePage(gvs, blkno, me->leftBlkno, me->parent->blkno, myoff, me->parent->isRoot);
			meDelete = TRUE;
		}
	}

	ReleaseBuffer(buffer);

	if (!meDelete)
		me->leftBlkno = blkno;

	return meDelete;
}
Example #3
0
/*
 * 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)
{
	Buffer		buffer;
	Page		page;
	bool		hasVoidPage = FALSE;
	MemoryContext oldCxt;

	buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
								RBM_NORMAL, gvs->strategy);
	page = BufferGetPage(buffer);

	ginTraverseLock(buffer, false);

	Assert(GinPageIsData(page));

	if (GinPageIsLeaf(page))
	{
		oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
		ginVacuumPostingTreeLeaf(gvs->index, buffer, 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++)
		{
			PostingItem *pitem = GinDataPageGetPostingItem(page, i);

			children[i] = PostingItemGetBlockNumber(pitem);
		}

		UnlockReleaseBuffer(buffer);

		for (i = FirstOffsetNumber; i <= maxoff; i++)
		{
			if (ginVacuumPostingTreeLeaves(gvs, children[i], FALSE))
				hasEmptyChild = TRUE;
			else
				hasNonEmptyChild = TRUE;
		}

		pfree(children);

		vacuum_delay_point();

		/*
		 * All subtree is empty - just return TRUE to indicate that parent
		 * must do a cleanup. Unless we are ROOT an there is way to go upper.
		 */

		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;
	}
}