vacuum-secondphase-setvm-v1.patch
application/octet-stream
Filename: vacuum-secondphase-setvm-v1.patch
Type: application/octet-stream
Part: 0
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 | 19 | 0 |
| src/backend/access/heap/pruneheap.c | 7 | 0 |
| src/backend/commands/vacuumlazy.c | 71 | 0 |
| src/include/storage/bufpage.h | 39 | 2 |
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 74c41fa..cd76158 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1945,6 +1945,16 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
ItemPointerGetBlockNumber(&(heaptup->t_self)),
vmbuffer);
}
+ else if (PageIsAllVisibleOrDead(BufferGetPage(buffer)))
+ {
+ /*
+ * We don't need to worry about WAL-logging this action. The only place
+ * where this flag is consulted is in the second phase of vacuum and
+ * the first phase of vacuum will take care of clearing/setting the
+ * flag appropriately
+ */
+ PageClearAllVisibleOrDead(BufferGetPage(buffer));
+ }
/*
* XXX Should we set PageSetPrunable on this page ?
@@ -2204,6 +2214,8 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
BufferGetBlockNumber(buffer),
vmbuffer);
}
+ else if (PageIsAllVisibleOrDead(page))
+ PageClearAllVisibleOrDead(page);
/*
* XXX Should we set PageSetPrunable on this page ? See heap_insert()
@@ -2585,6 +2597,8 @@ l1:
visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
vmbuffer);
}
+ else if (PageIsAllVisibleOrDead(page))
+ PageClearAllVisibleOrDead(page);
/* store transaction information of xact deleting the tuple */
tp.t_data->t_infomask &= ~(HEAP_XMAX_COMMITTED |
@@ -3197,6 +3211,9 @@ l2:
visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
vmbuffer);
}
+ else if (PageIsAllVisibleOrDead(BufferGetPage(buffer)))
+ PageClearAllVisibleOrDead(BufferGetPage(buffer));
+
if (newbuf != buffer && PageIsAllVisible(BufferGetPage(newbuf)))
{
all_visible_cleared_new = true;
@@ -3204,6 +3221,8 @@ l2:
visibilitymap_clear(relation, BufferGetBlockNumber(newbuf),
vmbuffer_new);
}
+ else if (PageIsAllVisibleOrDead(BufferGetPage(newbuf)))
+ PageClearAllVisibleOrDead(BufferGetPage(newbuf));
if (newbuf != buffer)
MarkBufferDirty(newbuf);
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 97a2868..221ddf0 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -222,6 +222,13 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid;
/*
+ * If we don't find new prune xid, we assume that there is nothing more
+ * do on the page and so clear the prunable flag
+ */
+ if (!TransactionIdIsValid(prstate.new_prune_xid))
+ PageClearPrunable(page);
+
+ /*
* 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.
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 5036849..ea9cfa5 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -458,6 +458,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
Size freespace;
bool all_visible_according_to_vm;
bool all_visible;
+ bool all_visible_or_dead;
bool has_dead_tuples;
TransactionId visibility_cutoff_xid = InvalidTransactionId;
@@ -661,6 +662,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
* requiring freezing.
*/
all_visible = true;
+ all_visible_or_dead = true;
has_dead_tuples = false;
nfrozen = 0;
hastup = false;
@@ -759,6 +761,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
if (!(tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED))
{
all_visible = false;
+ all_visible_or_dead = false;
break;
}
@@ -770,6 +773,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
if (!TransactionIdPrecedes(xmin, OldestXmin))
{
all_visible = false;
+ all_visible_or_dead = false;
break;
}
@@ -786,14 +790,17 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
*/
nkeep += 1;
all_visible = false;
+ all_visible_or_dead = false;
break;
case HEAPTUPLE_INSERT_IN_PROGRESS:
/* This is an expected case during concurrent vacuum */
all_visible = false;
+ all_visible_or_dead = false;
break;
case HEAPTUPLE_DELETE_IN_PROGRESS:
/* This is an expected case during concurrent vacuum */
all_visible = false;
+ all_visible_or_dead = false;
break;
default:
elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
@@ -858,6 +865,24 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
*/
vacrelstats->num_dead_tuples = 0;
vacuumed_pages++;
+
+ /*
+ * If the page only contains either all-visible or dead tuples, the
+ * dead tuples themselves would have been removed by the
+ * lazy_vacuum_page(). So we can now mark the page all-visible
+ */
+ if (all_visible_or_dead)
+ all_visible = true;
+ }
+
+ /*
+ * Clear the PD_ALL_VISIBLE_OR_DEAD flag which might be left over from
+ * the previous failed vacuum. We may set it again below
+ */
+ if (PageIsAllVisibleOrDead(page))
+ {
+ PageClearAllVisibleOrDead(page);
+ MarkBufferDirty(buf);
}
freespace = PageGetHeapFreeSpace(page);
@@ -921,6 +946,23 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
MarkBufferDirty(buf);
visibilitymap_clear(onerel, blkno, vmbuffer);
}
+ /*
+ * If the page contains only all-visible and dead tuples, remember that
+ * information in the page header. The page will get marked all-visible
+ * if no new actions happens between now and the second phase of
+ * vacuum. But the visibilitymap_set() function also needs the
+ * cutoff-xid for conflict resolution at the Hot Standby. We reuse the
+ * pd_prune_xid field in the page header to store this cutoff-xid.
+ *
+ * Note: We don't override the pd_prune_xid if its already set. This is
+ * probably not required since the page does not contain any
+ * prunable items so the pd_prune_xid is of little use.
+ */
+ else if (all_visible_or_dead && !PageXidIsPruneXid(page))
+ {
+ PageSetAllVisibleOrDead(page, visibility_cutoff_xid);
+ MarkBufferDirty(buf);
+ }
UnlockReleaseBuffer(buf);
@@ -1096,6 +1138,35 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
unused[uncnt++] = toff;
}
+ /*
+ * The page is marked as PD_ALL_VISIBLE_OR_DEAD. The dead tuples we
+ * just removed, so what remains must be all-visible. Mark the page
+ * all-visible and also update the visibility map.
+ */
+ if (PageIsAllVisibleOrDead(page))
+ {
+ Buffer vmbuffer = InvalidBuffer;
+
+ /*
+ * We had at least one dead tuple on this page. So we must not have
+ * marked this page as all-visible in the visibility map. But if we
+ * had, then at least show a WARNING here
+ */
+ if (visibilitymap_test(onerel, blkno, &vmbuffer))
+ elog(WARNING, "page is not marked all-visible but visibility map bit is set in relation \"%s\" page %u",
+ RelationGetRelationName(onerel), blkno);
+ else
+ visibilitymap_set(onerel, blkno, InvalidXLogRecPtr, vmbuffer,
+ ((PageHeader) (page))->pd_prune_xid);
+
+ PageClearAllVisibleOrDead(page);
+ PageSetAllVisible(page);
+
+ if (BufferIsValid(vmbuffer))
+ ReleaseBuffer(vmbuffer);
+ vmbuffer = InvalidBuffer;
+ }
+
PageRepairFragmentation(page);
MarkBufferDirty(buffer);
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index fc3a69b..c2ab194 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -156,14 +156,22 @@ typedef PageHeaderData *PageHeader;
* PD_PAGE_FULL is set if an UPDATE doesn't find enough free space in the
* page for its new tuple version; this suggests that a prune is needed.
* Again, this is just a hint.
+ *
+ * PD_ALL_VISIBLE_OR_DEAD is set if the first phase of VACUUM encounters a page
+ * which is all-visible except one or more dead tuples which will be removed by
+ * the second phase of VACUUM.
*/
#define PD_HAS_FREE_LINES 0x0001 /* are there any unused line pointers? */
#define PD_PAGE_FULL 0x0002 /* not enough free space for new
* tuple? */
#define PD_ALL_VISIBLE 0x0004 /* all tuples on page are visible to
* everyone */
+#define PD_ALL_VISIBLE_OR_DEAD 0x0008 /* tuples on the page are either
+ visible to everyone or dead for
+ everyone */
+#define PD_XID_IS_PRUNEXID 0x0010 /* pd_prune_xid is prune xid */
-#define PD_VALID_FLAG_BITS 0x0007 /* OR of all valid pd_flags bits */
+#define PD_VALID_FLAG_BITS 0x001F /* OR of all valid pd_flags bits */
/*
* Page layout version number 0 is for pre-7.3 Postgres releases.
@@ -354,9 +362,31 @@ typedef PageHeaderData *PageHeader;
#define PageClearAllVisible(page) \
(((PageHeader) (page))->pd_flags &= ~PD_ALL_VISIBLE)
+#define PageXidIsPruneXid(page) \
+ (((PageHeader) (page))->pd_flags & PD_XID_IS_PRUNEXID)
+#define PageSetXidIsPruneXid(page) \
+ (((PageHeader) (page))->pd_flags |= PD_XID_IS_PRUNEXID)
+#define PageClearXidIsPruneXid(page) \
+ (((PageHeader) (page))->pd_flags &= ~PD_XID_IS_PRUNEXID)
+
+#define PageIsAllVisibleOrDead(page) \
+ (((PageHeader) (page))->pd_flags & PD_ALL_VISIBLE_OR_DEAD)
+#define PageSetAllVisibleOrDead(page, xid) \
+do { \
+ AssertMacro(!PageXidIsPruneXid(page)); \
+ (((PageHeader) (page))->pd_flags |= PD_ALL_VISIBLE_OR_DEAD); \
+ ((PageHeader) (page))->pd_prune_xid = (xid); \
+} while (0)
+#define PageClearAllVisibleOrDead(page) \
+do { \
+ (((PageHeader) (page))->pd_flags &= ~PD_ALL_VISIBLE_OR_DEAD); \
+ ((PageHeader) (page))->pd_prune_xid = InvalidTransactionId; \
+} while (0)
+
#define PageIsPrunable(page, oldestxmin) \
( \
AssertMacro(TransactionIdIsNormal(oldestxmin)), \
+ PageXidIsPruneXid(page) && \
TransactionIdIsValid(((PageHeader) (page))->pd_prune_xid) && \
TransactionIdPrecedes(((PageHeader) (page))->pd_prune_xid, oldestxmin) \
)
@@ -364,11 +394,18 @@ typedef PageHeaderData *PageHeader;
do { \
Assert(TransactionIdIsNormal(xid)); \
if (!TransactionIdIsValid(((PageHeader) (page))->pd_prune_xid) || \
+ !PageXidIsPruneXid(page) || \
TransactionIdPrecedes(xid, ((PageHeader) (page))->pd_prune_xid)) \
+ { \
((PageHeader) (page))->pd_prune_xid = (xid); \
+ PageSetXidIsPruneXid(page); \
+ } \
} while (0)
#define PageClearPrunable(page) \
- (((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
+do { \
+ (((PageHeader) (page))->pd_prune_xid = InvalidTransactionId); \
+ PageClearXidIsPruneXid(page); \
+} while (0)
/* ----------------------------------------------------------------