Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gin_delete.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
delete from box where uid in (select uid from box limit 200000);
14 changes: 14 additions & 0 deletions gin_init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CREATE TABLE BOX (uid bigint,
lids integer[] NOT NULL
CHECK (array_ndims(lids) = 1));

CREATE OR REPLACE FUNCTION ulids(
i_uid bigint,
i_lids integer[]
) RETURNS bigint[] AS $$
SELECT array_agg((i_uid << 32) | lid)
FROM unnest(i_lids) lid;
$$ LANGUAGE SQL IMMUTABLE STRICT;

CREATE INDEX i_box_uid_lids
ON box USING gin (ulids(uid, lids)) WITH (FASTUPDATE=OFF);
12 changes: 12 additions & 0 deletions gin_shot.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
\setrandom uid 1 1500000
--\set uid 777
\setrandom lid 1 1000
\setrandom lid2 1 1000
\setrandom lid3 1 1000
--\set lid 1
--\set lid2 2
--\set lid3 3
BEGIN;
insert into box values(:uid,'{:lid,:lid2,:lid3}');
insert into box values(:uid,'{}');
END;
3 changes: 3 additions & 0 deletions gin_vacuum.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
select pg_size_pretty(pg_relation_size('i_box_uid_lids'));
vacuum box;
select pg_size_pretty(pg_relation_size('i_box_uid_lids'));
2 changes: 2 additions & 0 deletions prevacuum.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
../project/bin/psql postgres<gin_delete.sql
2 changes: 1 addition & 1 deletion src/backend/access/gin/ginbtree.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ static void ginFinishSplit(GinBtree btree, GinBtreeStack *stack,
/*
* Lock buffer by needed method for search.
*/
static int
int
ginTraverseLock(Buffer buffer, bool searchMode)
{
Page page;
Expand Down
220 changes: 115 additions & 105 deletions src/backend/access/gin/ginvacuum.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* delete & vacuum routines for the postgres GIN
*
*
* Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
Expand Down Expand Up @@ -108,75 +108,17 @@ xlogVacuumPage(Relation index, Buffer buffer)
PageSetLSN(page, recptr);
}

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;
}
typedef struct DataPageDeleteStack
{
struct DataPageDeleteStack *child;
struct DataPageDeleteStack *parent;

/*
* 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;
}
BlockNumber blkno; /* current block number */
BlockNumber leftBlkno; /* rightest non-deleted page on left */
bool isRoot;
} DataPageDeleteStack;

return hasVoidPage;
}

/*
* Delete a posting tree page.
Expand All @@ -195,6 +137,8 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
/*
* Lock the pages in the same order as an insertion would, to avoid
* deadlocks: left, then right, then parent.
*
* AB: WE HAVE CLEANUP LOCK, NO INSERTS HERE
*/
lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
RBM_NORMAL, gvs->strategy);
Expand All @@ -204,10 +148,10 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
RBM_NORMAL, gvs->strategy);

LockBuffer(lBuffer, GIN_EXCLUSIVE);
LockBuffer(dBuffer, GIN_EXCLUSIVE);
if (!isParentRoot) /* parent is already locked by
* LockBufferForCleanup() */
LockBuffer(pBuffer, GIN_EXCLUSIVE);
//LockBuffer(dBuffer, GIN_EXCLUSIVE);
//if (!isParentRoot) /* parent is already locked by
// * LockBufferForCleanup() */
// LockBuffer(pBuffer, GIN_EXCLUSIVE);

START_CRIT_SECTION();

Expand Down Expand Up @@ -271,26 +215,17 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
PageSetLSN(BufferGetPage(lBuffer), recptr);
}

if (!isParentRoot)
LockBuffer(pBuffer, GIN_UNLOCK);
//if (!isParentRoot)
// LockBuffer(pBuffer, GIN_UNLOCK);
ReleaseBuffer(pBuffer);
UnlockReleaseBuffer(lBuffer);
UnlockReleaseBuffer(dBuffer);
ReleaseBuffer(dBuffer);//UnlockReleaseBuffer(dBuffer);

END_CRIT_SECTION();

gvs->result->pages_deleted++;
}

typedef struct DataPageDeleteStack
{
struct DataPageDeleteStack *child;
struct DataPageDeleteStack *parent;

BlockNumber blkno; /* current block number */
BlockNumber leftBlkno; /* rightest non-deleted page on left */
bool isRoot;
} DataPageDeleteStack;

/*
* scans posting tree and deletes empty pages
Expand Down Expand Up @@ -324,6 +259,10 @@ ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,

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

if(!isRoot)
LockBuffer(buffer, GIN_EXCLUSIVE);

page = BufferGetPage(buffer);

Assert(GinPageIsData(page));
Expand Down Expand Up @@ -358,6 +297,9 @@ ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
}
}

if(!isRoot)
LockBuffer(buffer, GIN_UNLOCK);

ReleaseBuffer(buffer);

if (!meDelete)
Expand All @@ -366,37 +308,106 @@ ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
return meDelete;
}

static void
ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)

static bool
ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno, bool isRoot)
{
Buffer rootBuffer = InvalidBuffer;
DataPageDeleteStack root,
*ptr,
*tmp;
Buffer buffer;
Page page;
bool hasVoidPage = FALSE;
MemoryContext oldCxt;

if (ginVacuumPostingTreeLeaves(gvs, rootBlkno, TRUE, &rootBuffer) == FALSE)
buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
RBM_NORMAL, gvs->strategy);
page = BufferGetPage(buffer);

ginTraverseLock(buffer,false);

Assert(GinPageIsData(page));

if (GinPageIsLeaf(page))
{
Assert(rootBuffer == InvalidBuffer);
return;
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 isChildHasVoid = FALSE;
bool isanyonealife = FALSE;
OffsetNumber maxoff = GinPageGetOpaque(page)->maxoff;
BlockNumber* children = palloc(sizeof(BlockNumber) * (maxoff + 1));

memset(&root, 0, sizeof(DataPageDeleteStack));
root.leftBlkno = InvalidBlockNumber;
root.isRoot = TRUE;
for (i = FirstOffsetNumber; i <= maxoff; i++)
{
PostingItem *pitem = GinDataPageGetPostingItem(page, i);

vacuum_delay_point();
children[i] = PostingItemGetBlockNumber(pitem);
}

ginScanToDelete(gvs, rootBlkno, TRUE, &root, InvalidOffsetNumber);
UnlockReleaseBuffer(buffer);

ptr = root.child;
while (ptr)
{
tmp = ptr->child;
pfree(ptr);
ptr = tmp;
for (i = FirstOffsetNumber; i <= maxoff; i++)
{
if (ginVacuumPostingTreeLeaves(gvs, children[i], FALSE))
isChildHasVoid = TRUE;
else
isanyonealife = TRUE;
}

pfree(children); // seems it's good to clean up here, at least root caller did it

vacuum_delay_point();

if(isChildHasVoid && !isanyonealife && !isRoot) // we have to kill this page
return TRUE; // let the parent do cleanup locked deletion

if(isChildHasVoid)
{
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);
}

return FALSE;
}
}

UnlockReleaseBuffer(rootBuffer);
static void
ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
{
ginVacuumPostingTreeLeaves(gvs, rootBlkno, TRUE);
}

/*
Expand Down Expand Up @@ -512,7 +523,6 @@ ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint3

return (tmppage == origpage) ? NULL : tmppage;
}

Datum
ginbulkdelete(PG_FUNCTION_ARGS)
{
Expand Down
2 changes: 2 additions & 0 deletions src/include/access/gin_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -973,4 +973,6 @@ ginCompareItemPointers(ItemPointer a, ItemPointer b)
#define ginCompareItemPointers(a, b) ItemPointerCompare(a, b)
#endif /* PG_USE_INLINE */

extern int ginTraverseLock(Buffer buffer, bool searchMode);

#endif /* GIN_PRIVATE_H */
2 changes: 2 additions & 0 deletions status.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
../project/bin/psql postgres<gin_vacuum.sql
4 changes: 4 additions & 0 deletions test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
../project/bin/psql postgres<gin_init.sql

../project/bin/pgbench -n -j 32 -c 32 -T 3600 -f gin_shot.sql postgres