vacuum-unstick-v1.patch
application/octet-stream
Filename: vacuum-unstick-v1.patch
Type: application/octet-stream
Part: 0
Message:
Re: heap vacuum & cleanup locks
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v1
| File | + | − |
|---|---|---|
| src/backend/access/heap/heapam.c | 10 | 3 |
| src/backend/access/heap/pruneheap.c | 41 | 21 |
| src/backend/commands/vacuumlazy.c | 33 | 16 |
| src/backend/storage/buffer/bufmgr.c | 44 | 0 |
| src/include/access/heapam.h | 5 | 3 |
| src/include/access/htup.h | 4 | 0 |
| src/include/storage/bufmgr.h | 1 | 0 |
| src/include/storage/bufpage.h | 9 | 1 |
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index b2d1901..b9a057e 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3992,7 +3992,7 @@ log_heap_clean(Relation reln, Buffer buffer,
OffsetNumber *redirected, int nredirected,
OffsetNumber *nowdead, int ndead,
OffsetNumber *nowunused, int nunused,
- TransactionId latestRemovedXid)
+ TransactionId latestRemovedXid, bool defragment)
{
xl_heap_clean xlrec;
uint8 info;
@@ -4005,6 +4005,7 @@ log_heap_clean(Relation reln, Buffer buffer,
xlrec.node = reln->rd_node;
xlrec.block = BufferGetBlockNumber(buffer);
xlrec.latestRemovedXid = latestRemovedXid;
+ xlrec.flags = defragment ? HEAP_CLEAN_DEFRAGMENT : 0;
xlrec.nredirected = nredirected;
xlrec.ndead = ndead;
@@ -4308,6 +4309,7 @@ heap_xlog_clean(XLogRecPtr lsn, XLogRecord *record)
int ndead;
int nunused;
Size freespace;
+ bool defragment;
/*
* We're about to remove tuples. In Hot Standby mode, ensure that there's
@@ -4325,11 +4327,15 @@ heap_xlog_clean(XLogRecPtr lsn, XLogRecord *record)
if (record->xl_info & XLR_BKP_BLOCK_1)
return;
+ defragment = (xlrec->flags & HEAP_CLEAN_DEFRAGMENT) != 0;
buffer = XLogReadBufferExtended(xlrec->node, MAIN_FORKNUM, xlrec->block, RBM_NORMAL);
if (!BufferIsValid(buffer))
return;
- LockBufferForCleanup(buffer);
+ if (defragment)
+ LockBufferForCleanup(buffer);
+ else
+ LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
page = (Page) BufferGetPage(buffer);
if (XLByteLE(lsn, PageGetLSN(page)))
@@ -4351,7 +4357,8 @@ heap_xlog_clean(XLogRecPtr lsn, XLogRecord *record)
heap_page_prune_execute(buffer,
redirected, nredirected,
nowdead, ndead,
- nowunused, nunused);
+ nowunused, nunused,
+ defragment);
freespace = PageGetHeapFreeSpace(page); /* needed to update FSM below */
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 61f2ce4..f3fecce 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -108,15 +108,19 @@ heap_page_prune_opt(Relation relation, Buffer buffer, TransactionId OldestXmin)
if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
{
- /* OK, try to get exclusive buffer lock */
+ /*
+ * Try to get buffer cleanup lock. There's little point in pruning
+ * the page unless we can also defragment.
+ */
if (!ConditionalLockBufferForCleanup(buffer))
return;
/*
* Now that we have buffer lock, get accurate information about the
* page's free space, and recheck the heuristic about whether to
- * prune. (We needn't recheck PageIsPrunable, since no one else could
- * have pruned while we hold pin.)
+ * prune. (We don't recheck PageIsPrunable(); if vacuum cleaned up
+ * the page despite our pin, it will necessarily have skipped
+ * the defrag step, so we may as well do it now.)
*/
if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
{
@@ -124,7 +128,8 @@ heap_page_prune_opt(Relation relation, Buffer buffer, TransactionId OldestXmin)
* needed */
/* OK to prune */
- (void) heap_page_prune(relation, buffer, OldestXmin, true, &ignore);
+ (void) heap_page_prune(relation, buffer, OldestXmin, true, &ignore,
+ true);
}
/* And release buffer lock */
@@ -134,9 +139,10 @@ heap_page_prune_opt(Relation relation, Buffer buffer, TransactionId OldestXmin)
/*
- * Prune and repair fragmentation in the specified page.
+ * Prune and (optionally) repair fragmentation in the specified page.
*
- * Caller must have pin and buffer cleanup lock on the page.
+ * Caller must have pin and exclusive lock on the page. To defragment, a
+ * cleanup lock is required.
*
* OldestXmin is the cutoff XID used to distinguish whether tuples are DEAD
* or RECENTLY_DEAD (see HeapTupleSatisfiesVacuum).
@@ -151,7 +157,8 @@ heap_page_prune_opt(Relation relation, Buffer buffer, TransactionId OldestXmin)
*/
int
heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
- bool report_stats, TransactionId *latestRemovedXid)
+ bool report_stats, TransactionId *latestRemovedXid,
+ bool defragment)
{
int ndeleted = 0;
Page page = BufferGetPage(buffer);
@@ -201,8 +208,9 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
/* Any error while applying the changes is critical */
START_CRIT_SECTION();
- /* Have we found any prunable items? */
- if (prstate.nredirected > 0 || prstate.ndead > 0 || prstate.nunused > 0)
+ /* Have we found any prunable items (or do we need to defrag anyway)? */
+ if (prstate.nredirected > 0 || prstate.ndead > 0 || prstate.nunused > 0
+ || (defragment && PageNeedsDefrag(page)))
{
/*
* Apply the planned item changes, then repair page fragmentation, and
@@ -211,7 +219,8 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
heap_page_prune_execute(buffer,
prstate.redirected, prstate.nredirected,
prstate.nowdead, prstate.ndead,
- prstate.nowunused, prstate.nunused);
+ prstate.nowunused, prstate.nunused,
+ defragment);
/*
* Update the page's pd_prune_xid field to either zero, or the lowest
@@ -220,11 +229,21 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid;
/*
- * Also clear the "page is full" flag, since there's no point in
- * repeating the prune/defrag process until something else happens to
- * the page.
+ * If we were able to defragment, also clear the "page is full" flag,
+ * since there's no point in repeating this work something else happens
+ * to the page; also clear the "needs defrag" flag, if set.
+ *
+ * If we were not able to defragment, set the "needs defrag" flag so
+ * that the next vacuum will try to clean it up even if no new dead
+ * tuples have been created.
*/
- PageClearFull(page);
+ if (defragment)
+ {
+ PageClearFull(page);
+ PageClearNeedsDefrag(page);
+ }
+ else
+ PageSetNeedsDefrag(page);
MarkBufferDirty(buffer);
@@ -239,7 +258,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
prstate.redirected, prstate.nredirected,
prstate.nowdead, prstate.ndead,
prstate.nowunused, prstate.nunused,
- prstate.latestRemovedXid);
+ prstate.latestRemovedXid, defragment);
PageSetLSN(BufferGetPage(buffer), recptr);
PageSetTLI(BufferGetPage(buffer), ThisTimeLineID);
@@ -252,15 +271,15 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
* pd_prune_xid field, update it and mark the buffer dirty. This is
* treated as a non-WAL-logged hint.
*
- * Also clear the "page is full" flag if it is set, since there's no
- * point in repeating the prune/defrag process until something else
- * happens to the page.
+ * Also clear the "page is full" flag, since there's no point in
+ * repeating this work something else happens to the page.
*/
if (((PageHeader) page)->pd_prune_xid != prstate.new_prune_xid ||
PageIsFull(page))
{
((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid;
- PageClearFull(page);
+ if (defragment)
+ PageClearFull(page);
SetBufferCommitInfoNeedsSave(buffer);
}
}
@@ -643,7 +662,7 @@ void
heap_page_prune_execute(Buffer buffer,
OffsetNumber *redirected, int nredirected,
OffsetNumber *nowdead, int ndead,
- OffsetNumber *nowunused, int nunused)
+ OffsetNumber *nowunused, int nunused, bool defragment)
{
Page page = (Page) BufferGetPage(buffer);
OffsetNumber *offnum;
@@ -684,7 +703,8 @@ heap_page_prune_execute(Buffer buffer,
* Finally, repair any fragmentation, and update the page's hint bit about
* whether it has free pointers.
*/
- PageRepairFragmentation(page);
+ if (defragment)
+ PageRepairFragmentation(page);
}
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index b197b45..c794dc2 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -124,7 +124,7 @@ static void lazy_cleanup_index(Relation indrel,
IndexBulkDeleteResult *stats,
LVRelStats *vacrelstats);
static int lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
- int tupindex, LVRelStats *vacrelstats);
+ int tupindex, LVRelStats *vacrelstats, bool defragment);
static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats);
static BlockNumber count_nondeletable_pages(Relation onerel,
LVRelStats *vacrelstats);
@@ -418,6 +418,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
bool all_visible_according_to_vm;
bool all_visible;
bool has_dead_tuples;
+ bool defragment;
if (blkno == next_not_all_visible_block)
{
@@ -485,8 +486,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
buf = ReadBufferExtended(onerel, MAIN_FORKNUM, blkno,
RBM_NORMAL, vac_strategy);
- /* We need buffer cleanup lock so that we can prune HOT chains. */
- LockBufferForCleanup(buf);
+ /*
+ * If we can get a buffer cleanup lock, we'll prune and defragment
+ * the page. But if someone's holding a pin, we don't want to get
+ * stuck waiting for it, so we'll just prune and leave defragmentation
+ * for another time.
+ */
+ defragment = LockBufferForPossibleCleanup(buf);
page = BufferGetPage(buf);
@@ -567,7 +573,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
* We count tuples removed by the pruning step as removed by VACUUM.
*/
tups_vacuumed += heap_page_prune(onerel, buf, OldestXmin, false,
- &vacrelstats->latestRemovedXid);
+ &vacrelstats->latestRemovedXid,
+ defragment);
/*
* Now scan the page to collect vacuumable items and check for tuples
@@ -759,7 +766,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
vacrelstats->num_dead_tuples > 0)
{
/* Remove tuples from heap */
- lazy_vacuum_page(onerel, blkno, buf, 0, vacrelstats);
+ lazy_vacuum_page(onerel, blkno, buf, 0, vacrelstats, defragment);
/*
* Forget the now-vacuumed tuples, and press on, but be careful
@@ -772,8 +779,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
freespace = PageGetHeapFreeSpace(page);
- /* Update the all-visible flag on the page */
- if (!PageIsAllVisible(page) && all_visible)
+ /*
+ * Update the all-visible flag on the page. We skip this if we weren't
+ * able to defragment, so that the next vacuum will (hopefully) do so.
+ */
+ if (!PageIsAllVisible(page) && all_visible && defragment)
{
PageSetAllVisible(page);
SetBufferCommitInfoNeedsSave(buf);
@@ -926,21 +936,27 @@ lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats)
Buffer buf;
Page page;
Size freespace;
+ bool defragment;
vacuum_delay_point();
tblk = ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);
buf = ReadBufferExtended(onerel, MAIN_FORKNUM, tblk, RBM_NORMAL,
vac_strategy);
- LockBufferForCleanup(buf);
- tupindex = lazy_vacuum_page(onerel, tblk, buf, tupindex, vacrelstats);
+ defragment = LockBufferForPossibleCleanup(buf);
+ tupindex = lazy_vacuum_page(onerel, tblk, buf, tupindex, vacrelstats,
+ defragment);
- /* Now that we've compacted the page, record its available space */
- page = BufferGetPage(buf);
- freespace = PageGetHeapFreeSpace(page);
+ /* If we've compacted the page, record its available space */
+ if (defragment)
+ {
+ page = BufferGetPage(buf);
+ freespace = PageGetHeapFreeSpace(page);
+ }
UnlockReleaseBuffer(buf);
- RecordPageWithFreeSpace(onerel, tblk, freespace);
+ if (defragment)
+ RecordPageWithFreeSpace(onerel, tblk, freespace);
npages++;
}
@@ -964,7 +980,7 @@ lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats)
*/
static int
lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
- int tupindex, LVRelStats *vacrelstats)
+ int tupindex, LVRelStats *vacrelstats, bool defragment)
{
Page page = BufferGetPage(buffer);
OffsetNumber unused[MaxOffsetNumber];
@@ -987,7 +1003,8 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
unused[uncnt++] = toff;
}
- PageRepairFragmentation(page);
+ if (defragment)
+ PageRepairFragmentation(page);
MarkBufferDirty(buffer);
@@ -999,7 +1016,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
recptr = log_heap_clean(onerel, buffer,
NULL, 0, NULL, 0,
unused, uncnt,
- vacrelstats->latestRemovedXid);
+ vacrelstats->latestRemovedXid, defragment);
PageSetLSN(page, recptr);
PageSetTLI(page, ThisTimeLineID);
}
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index e59af33..093f4a3 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -2564,6 +2564,50 @@ ConditionalLockBufferForCleanup(Buffer buffer)
return false;
}
+/*
+ * LockBufferForPossibleCleanup
+ *
+ * Exclusive lock the target buffer. If the pin count happens to be exactly
+ * one, then return true, indicating that we've acquired a cleanup lock. If
+ * not, return false, but retain the buffer lock.
+ */
+bool
+LockBufferForPossibleCleanup(Buffer buffer)
+{
+ volatile BufferDesc *bufHdr;
+ bool got_cleanup_lock;
+
+ Assert(BufferIsValid(buffer));
+ Assert(PinCountWaitBuf == NULL);
+
+ if (BufferIsLocal(buffer))
+ {
+ /* There should be exactly one pin */
+ if (LocalRefCount[-buffer - 1] != 1)
+ elog(ERROR, "incorrect local pin count: %d",
+ LocalRefCount[-buffer - 1]);
+ /* Nobody else to wait for */
+ return true;
+ }
+
+ /* There should be exactly one local pin */
+ if (PrivateRefCount[buffer - 1] != 1)
+ elog(ERROR, "incorrect local pin count: %d",
+ PrivateRefCount[buffer - 1]);
+
+ bufHdr = &BufferDescriptors[buffer - 1];
+
+ /* Get buffer lock. */
+ LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+ /* Check whether it's a cleanup lock. */
+ LockBufHdr(bufHdr);
+ Assert(bufHdr->refcount > 0);
+ got_cleanup_lock = (bufHdr->refcount == 1);
+ UnlockBufHdr(bufHdr);
+
+ return got_cleanup_lock;
+}
/*
* Functions for buffer I/O handling
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 776ea5c..3a9417d 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -133,7 +133,7 @@ extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer,
OffsetNumber *redirected, int nredirected,
OffsetNumber *nowdead, int ndead,
OffsetNumber *nowunused, int nunused,
- TransactionId latestRemovedXid);
+ TransactionId latestRemovedXid, bool defragment);
extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
TransactionId cutoff_xid,
OffsetNumber *offsets, int offcnt);
@@ -147,11 +147,13 @@ extern void heap_page_prune_opt(Relation relation, Buffer buffer,
TransactionId OldestXmin);
extern int heap_page_prune(Relation relation, Buffer buffer,
TransactionId OldestXmin,
- bool report_stats, TransactionId *latestRemovedXid);
+ bool report_stats, TransactionId *latestRemovedXid,
+ bool defragment);
extern void heap_page_prune_execute(Buffer buffer,
OffsetNumber *redirected, int nredirected,
OffsetNumber *nowdead, int ndead,
- OffsetNumber *nowunused, int nunused);
+ OffsetNumber *nowunused, int nunused,
+ bool defragment);
extern void heap_get_root_tuples(Page page, OffsetNumber *root_offsets);
/* in heap/syncscan.c */
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index 966e2d0..0854ca6 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -689,6 +689,7 @@ typedef struct xl_heap_clean
RelFileNode node;
BlockNumber block;
TransactionId latestRemovedXid;
+ uint16 flags;
uint16 nredirected;
uint16 ndead;
/* OFFSET NUMBERS FOLLOW */
@@ -696,6 +697,9 @@ typedef struct xl_heap_clean
#define SizeOfHeapClean (offsetof(xl_heap_clean, ndead) + sizeof(uint16))
+#define HEAP_CLEAN_DEFRAGMENT 0x0001 /* defragment page during cleanup */
+
+
/*
* Cleanup_info is required in some cases during a lazy VACUUM.
* Used for reporting the results of HeapTupleHeaderAdvanceLatestRemovedXid()
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 49b5d31..ab568ca 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -208,6 +208,7 @@ extern void LockBuffer(Buffer buffer, int mode);
extern bool ConditionalLockBuffer(Buffer buffer);
extern void LockBufferForCleanup(Buffer buffer);
extern bool ConditionalLockBufferForCleanup(Buffer buffer);
+extern bool LockBufferForPossibleCleanup(Buffer buffer);
extern bool HoldingBufferPinThatDelaysRecovery(void);
extern void AbortBufferIO(void);
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 42d6b10..2d4c721 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -154,8 +154,9 @@ typedef PageHeaderData *PageHeader;
* tuple? */
#define PD_ALL_VISIBLE 0x0004 /* all tuples on page are visible to
* everyone */
+#define PD_NEEDS_DEFRAG 0x0008 /* page pruned without defrag */
-#define PD_VALID_FLAG_BITS 0x0007 /* OR of all valid pd_flags bits */
+#define PD_VALID_FLAG_BITS 0x000f /* OR of all valid pd_flags bits */
/*
* Page layout version number 0 is for pre-7.3 Postgres releases.
@@ -345,6 +346,13 @@ typedef PageHeaderData *PageHeader;
#define PageClearAllVisible(page) \
(((PageHeader) (page))->pd_flags &= ~PD_ALL_VISIBLE)
+#define PageNeedsDefrag(page) \
+ (((PageHeader) (page))->pd_flags & PD_NEEDS_DEFRAG)
+#define PageSetNeedsDefrag(page) \
+ (((PageHeader) (page))->pd_flags |= PD_NEEDS_DEFRAG)
+#define PageClearNeedsDefrag(page) \
+ (((PageHeader) (page))->pd_flags &= ~PD_NEEDS_DEFRAG)
+
#define PageIsPrunable(page, oldestxmin) \
( \
AssertMacro(TransactionIdIsNormal(oldestxmin)), \