1
0
mirror of https://github.com/postgres/postgres.git synced 2025-08-25 20:23:07 +03:00

Truncate line pointer array during VACUUM.

Teach VACUUM to truncate the line pointer array of each heap page when a
contiguous group of LP_UNUSED line pointers appear at the end of the
array -- these unused and unreferenced items are excluded.  This process
occurs during VACUUM's second pass over the heap, right after LP_DEAD
line pointers on the page (those encountered/pruned during the first
pass) are marked LP_UNUSED.

Truncation avoids line pointer bloat with certain workloads,
particularly those involving continual range DELETEs and bulk INSERTs
against the same table.

Also harden heapam code to check for an out-of-range page offset number
in places where we weren't already doing so.

Author: Matthias van de Meent <boekewurm+postgres@gmail.com>
Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-By: Masahiko Sawada <sawada.mshk@gmail.com>
Reviewed-By: Peter Geoghegan <pg@bowt.ie>
Discussion: https://postgr.es/m/CAEze2WjgaQc55Y5f5CQd3L=eS5CZcff2Obxp=O6pto8-f0hC4w@mail.gmail.com
Discussion: https://postgr.es/m/CAH2-Wzn6a64PJM1Ggzm=uvx2otsopJMhFQj_g1rAj4GWr3ZSzw@mail.gmail.com
This commit is contained in:
Peter Geoghegan
2021-04-07 08:47:15 -07:00
parent 3db826bd55
commit 3c3b8a4b26
5 changed files with 144 additions and 11 deletions

View File

@@ -250,8 +250,17 @@ PageAddItemExtended(Page page,
/* if no free slot, we'll put it at limit (1st open slot) */
if (PageHasFreeLinePointers(phdr))
{
/* Look for "recyclable" (unused) ItemId */
for (offsetNumber = 1; offsetNumber < limit; offsetNumber++)
/*
* Scan line pointer array to locate a "recyclable" (unused)
* ItemId.
*
* Always use earlier items first. PageTruncateLinePointerArray
* can only truncate unused items when they appear as a contiguous
* group at the end of the line pointer array.
*/
for (offsetNumber = FirstOffsetNumber;
offsetNumber < limit; /* limit is maxoff+1 */
offsetNumber++)
{
itemId = PageGetItemId(phdr, offsetNumber);
@@ -675,11 +684,23 @@ compactify_tuples(itemIdCompact itemidbase, int nitems, Page page, bool presorte
/*
* PageRepairFragmentation
*
* Frees fragmented space on a page.
* It doesn't remove unused line pointers! Please don't change this.
* Frees fragmented space on a heap page following pruning.
*
* This routine is usable for heap pages only, but see PageIndexMultiDelete.
*
* Never removes unused line pointers. PageTruncateLinePointerArray can
* safely remove some unused line pointers. It ought to be safe for this
* routine to free unused line pointers in roughly the same way, but it's not
* clear that that would be beneficial.
*
* PageTruncateLinePointerArray is only called during VACUUM's second pass
* over the heap. Any unused line pointers that it sees are likely to have
* been set to LP_UNUSED (from LP_DEAD) immediately before the time it is
* called. On the other hand, many tables have the vast majority of all
* required pruning performed opportunistically (not during VACUUM). And so
* there is, in general, a good chance that even large groups of unused line
* pointers that we see here will be recycled quickly.
*
* Caller had better have a super-exclusive lock on page's buffer. As a side
* effect the page's PD_HAS_FREE_LINES hint bit will be set or unset as
* needed.
@@ -784,6 +805,89 @@ PageRepairFragmentation(Page page)
PageClearHasFreeLinePointers(page);
}
/*
* PageTruncateLinePointerArray
*
* Removes unused line pointers at the end of the line pointer array.
*
* This routine is usable for heap pages only. It is called by VACUUM during
* its second pass over the heap. We expect at least one LP_UNUSED line
* pointer on the page (if VACUUM didn't have an LP_DEAD item on the page that
* it just set to LP_UNUSED then it should not call here).
*
* We avoid truncating the line pointer array to 0 items, if necessary by
* leaving behind a single remaining LP_UNUSED item. This is a little
* arbitrary, but it seems like a good idea to avoid leaving a PageIsEmpty()
* page behind.
*
* Caller can have either an exclusive lock or a super-exclusive lock on
* page's buffer. The page's PD_HAS_FREE_LINES hint bit will be set or unset
* based on whether or not we leave behind any remaining LP_UNUSED items.
*/
void
PageTruncateLinePointerArray(Page page)
{
PageHeader phdr = (PageHeader) page;
bool countdone = false,
sethint = false;
int nunusedend = 0;
/* Scan line pointer array back-to-front */
for (int i = PageGetMaxOffsetNumber(page); i >= FirstOffsetNumber; i--)
{
ItemId lp = PageGetItemId(page, i);
if (!countdone && i > FirstOffsetNumber)
{
/*
* Still determining which line pointers from the end of the array
* will be truncated away. Either count another line pointer as
* safe to truncate, or notice that it's not safe to truncate
* additional line pointers (stop counting line pointers).
*/
if (!ItemIdIsUsed(lp))
nunusedend++;
else
countdone = true;
}
else
{
/*
* Once we've stopped counting we still need to figure out if
* there are any remaining LP_UNUSED line pointers somewhere more
* towards the front of the array.
*/
if (!ItemIdIsUsed(lp))
{
/*
* This is an unused line pointer that we won't be truncating
* away -- so there is at least one. Set hint on page.
*/
sethint = true;
break;
}
}
}
if (nunusedend > 0)
{
phdr->pd_lower -= sizeof(ItemIdData) * nunusedend;
#ifdef CLOBBER_FREED_MEMORY
memset((char *) page + phdr->pd_lower, 0x7F,
sizeof(ItemIdData) * nunusedend);
#endif
}
else
Assert(sethint);
/* Set hint bit for PageAddItemExtended */
if (sethint)
PageSetHasFreeLinePointers(page);
else
PageClearHasFreeLinePointers(page);
}
/*
* PageGetFreeSpace
* Returns the size of the free (allocatable) space on a page,