Why doesn't Vacuum FULL update the VM

Started by Melanie Plagemanover 2 years ago4 messages
#1Melanie Plageman
melanieplageman@gmail.com

Hi,

I noticed that VACUUM FULL actually does freeze the tuples in the
rewritten table (heap_freeze_tuple()) but then it doesn't mark them
all visible or all frozen in the visibility map. I don't understand
why. It seems like it would save us future work.

Here is an example:

create extension pg_visibility;
drop table if exists foo;
create table foo(a int) with (autovacuum_enabled=false);
insert into foo select i%3 from generate_series(1,300)i;
update foo set a = 5 where a = 2;
select * from pg_visibility_map_summary('foo');
vacuum (verbose) foo;
select * from pg_visibility_map_summary('foo');
vacuum (full, verbose) foo;
select * from pg_visibility_map_summary('foo');

I don't see why the visibility map shouldn't be updated so that all of
the pages show all visible and all frozen for this relation after the
vacuum full.

- Melanie

#2Vik Fearing
vik@postgresfriends.org
In reply to: Melanie Plageman (#1)
Re: Why doesn't Vacuum FULL update the VM

On 9/1/23 21:34, Melanie Plageman wrote:

Hi,

I noticed that VACUUM FULL actually does freeze the tuples in the
rewritten table (heap_freeze_tuple()) but then it doesn't mark them
all visible or all frozen in the visibility map. I don't understand
why. It seems like it would save us future work.

I have often wondered this as well, but obviously I haven't done
anything about it.

I don't see why the visibility map shouldn't be updated so that all of
the pages show all visible and all frozen for this relation after the
vacuum full.

It cannot just blindly mark everything all visible and all frozen
because it will copy over dead tuples that concurrent transactions are
still allowed to see.
--
Vik Fearing

In reply to: Melanie Plageman (#1)
Re: Why doesn't Vacuum FULL update the VM

On Fri, Sep 1, 2023 at 12:34 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:

I don't see why the visibility map shouldn't be updated so that all of
the pages show all visible and all frozen for this relation after the
vacuum full.

There was a similar issue with COPY FREEZE. It was fixed relatively
recently -- see commit 7db0cd21.

--
Peter Geoghegan

#4Melanie Plageman
melanieplageman@gmail.com
In reply to: Peter Geoghegan (#3)
3 attachment(s)
Re: Why doesn't Vacuum FULL update the VM

On Fri, Sep 1, 2023 at 8:38 PM Peter Geoghegan <pg@bowt.ie> wrote:

On Fri, Sep 1, 2023 at 12:34 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:

I don't see why the visibility map shouldn't be updated so that all of
the pages show all visible and all frozen for this relation after the
vacuum full.

There was a similar issue with COPY FREEZE. It was fixed relatively
recently -- see commit 7db0cd21.

Thanks for digging that up for me!

My first thought after looking a bit at the vacuum full/cluster code
is that we could add an all_visible flag to the RewriteState and set
it to false in heapam_relation_copy_for_cluster() in roughly the same
cases as heap_page_is_all_visible(), then, if rwstate->all_visible is
true in raw_heap_insert(), when we need to advance to the next block,
we set the page all visible and update the VM. Either way, we reset
all_visible to true since we are advancing to the next block.

I wrote a rough outline of that idea in the attached patches. It
doesn't emit WAL for the VM update or handle toast tables or anything
(it is just a rough sketch), but I just wondered if this was in the
right direction.

- Melanie

Attachments:

v0-0003-set-all_visible-in-VM-vac-full.patchtext/x-patch; charset=US-ASCII; name=v0-0003-set-all_visible-in-VM-vac-full.patchDownload
From eda40534e169e0bac0d11d89947130b44653b925 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 3 Sep 2023 16:17:28 -0400
Subject: [PATCH v0 3/3] set all_visible in VM vac full

---
 src/backend/access/heap/heapam_handler.c | 15 ++++++++++++++-
 src/backend/access/heap/rewriteheap.c    | 16 ++++++++++++++++
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 5a17112c91..a37be8cfef 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -791,6 +791,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 		HeapTuple	tuple;
 		Buffer		buf;
 		bool		isdead;
+		TransactionId xmin;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -853,10 +854,18 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 				break;
 			case HEAPTUPLE_RECENTLY_DEAD:
 				*tups_recently_dead += 1;
-				/* fall through */
+				isdead = false;
+				rwstate->all_visible = false;
+				break;
 			case HEAPTUPLE_LIVE:
 				/* Live or recently dead, must copy it */
+				xmin = HeapTupleHeaderGetXmin(tuple->t_data);
 				isdead = false;
+				if (!HeapTupleHeaderXminCommitted(tuple->t_data))
+					rwstate->all_visible = false;
+
+				else if (!TransactionIdPrecedes(xmin, OldestXmin))
+					rwstate->all_visible = false;
 				break;
 			case HEAPTUPLE_INSERT_IN_PROGRESS:
 
@@ -873,6 +882,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 					elog(WARNING, "concurrent insert in progress within table \"%s\"",
 						 RelationGetRelationName(OldHeap));
 				/* treat as live */
+				rwstate->all_visible = false;
 				isdead = false;
 				break;
 			case HEAPTUPLE_DELETE_IN_PROGRESS:
@@ -886,6 +896,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 						 RelationGetRelationName(OldHeap));
 				/* treat as recently dead */
 				*tups_recently_dead += 1;
+				rwstate->all_visible = false;
 				isdead = false;
 				break;
 			default:
@@ -906,6 +917,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 				*tups_vacuumed += 1;
 				*tups_recently_dead -= 1;
 			}
+			else
+				rwstate->all_visible = false;
 			continue;
 		}
 
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 93e75dc7c2..b3d63200d4 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -111,6 +111,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
+#include "access/visibilitymap.h"
 #include "catalog/catalog.h"
 #include "common/file_utils.h"
 #include "lib/ilist.h"
@@ -234,6 +235,7 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm
 	state->rs_freeze_xid = freeze_xid;
 	state->rs_cutoff_multi = cutoff_multi;
 	state->rs_cxt = rw_cxt;
+	state->all_visible = true;
 
 	/* Initialize hash tables used to track update chains */
 	hash_ctl.keysize = sizeof(TidHashKey);
@@ -647,6 +649,8 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 			 * enforce saveFreeSpace unconditionally.
 			 */
 
+			if (state->all_visible)
+				PageSetAllVisible(page);
 			/* XLOG stuff */
 			if (RelationNeedsWAL(state->rs_new_rel))
 				log_newpage(&state->rs_new_rel->rd_locator,
@@ -655,6 +659,18 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 							page,
 							true);
 
+			if (state->all_visible)
+			{
+				Buffer vmbuffer = InvalidBuffer;
+				visibilitymap_pin(state->rs_new_rel, state->rs_blockno, &vmbuffer);
+				visibilitymap_set_unbuffered(state->rs_new_rel, state->rs_blockno, vmbuffer,
+									VISIBILITYMAP_ALL_VISIBLE | VISIBILITYMAP_ALL_FROZEN);
+				if (BufferIsValid(vmbuffer))
+					ReleaseBuffer(vmbuffer);
+			}
+
+			state->all_visible = true;
+
 			/*
 			 * Now write the page. We say skipFsync = true because there's no
 			 * need for smgr to schedule an fsync for this write; we'll do it
-- 
2.37.2

v0-0001-Extern-RewriteStateData.patchtext/x-patch; charset=US-ASCII; name=v0-0001-Extern-RewriteStateData.patchDownload
From 9d5f8b8f9ccf759df1bb078b106047c0e15e6f01 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 3 Sep 2023 16:14:21 -0400
Subject: [PATCH v0 1/3] Extern RewriteStateData

---
 src/backend/access/heap/rewriteheap.c | 29 -----------------------
 src/include/access/rewriteheap.h      | 33 +++++++++++++++++++++++++++
 2 files changed, 33 insertions(+), 29 deletions(-)

diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 424958912c..93e75dc7c2 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -125,35 +125,6 @@
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
-/*
- * State associated with a rewrite operation. This is opaque to the user
- * of the rewrite facility.
- */
-typedef struct RewriteStateData
-{
-	Relation	rs_old_rel;		/* source heap */
-	Relation	rs_new_rel;		/* destination heap */
-	Page		rs_buffer;		/* page currently being built */
-	BlockNumber rs_blockno;		/* block where page will go */
-	bool		rs_buffer_valid;	/* T if any tuples in buffer */
-	bool		rs_logical_rewrite; /* do we need to do logical rewriting */
-	TransactionId rs_oldest_xmin;	/* oldest xmin used by caller to determine
-									 * tuple visibility */
-	TransactionId rs_freeze_xid;	/* Xid that will be used as freeze cutoff
-									 * point */
-	TransactionId rs_logical_xmin;	/* Xid that will be used as cutoff point
-									 * for logical rewrites */
-	MultiXactId rs_cutoff_multi;	/* MultiXactId that will be used as cutoff
-									 * point for multixacts */
-	MemoryContext rs_cxt;		/* for hash tables and entries and tuples in
-								 * them */
-	XLogRecPtr	rs_begin_lsn;	/* XLogInsertLsn when starting the rewrite */
-	HTAB	   *rs_unresolved_tups; /* unmatched A tuples */
-	HTAB	   *rs_old_new_tid_map; /* unmatched B tuples */
-	HTAB	   *rs_logical_mappings;	/* logical remapping files */
-	uint32		rs_num_rewrite_mappings;	/* # in memory mappings */
-}			RewriteStateData;
-
 /*
  * The lookup keys for the hash tables are tuple TID and xmin (we must check
  * both to avoid false matches from dead tuples).  Beware that there is
diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h
index 1125457053..d5108ba2d8 100644
--- a/src/include/access/rewriteheap.h
+++ b/src/include/access/rewriteheap.h
@@ -14,13 +14,46 @@
 #define REWRITE_HEAP_H
 
 #include "access/htup.h"
+#include "access/xlogdefs.h"
 #include "storage/itemptr.h"
 #include "storage/relfilelocator.h"
+#include "storage/bufpage.h"
 #include "utils/relcache.h"
+#include "utils/hsearch.h"
 
 /* struct definition is private to rewriteheap.c */
 typedef struct RewriteStateData *RewriteState;
 
+/*
+ * State associated with a rewrite operation.
+ */
+typedef struct RewriteStateData
+{
+	Relation	rs_old_rel;		/* source heap */
+	Relation	rs_new_rel;		/* destination heap */
+	Page		rs_buffer;		/* page currently being built */
+	BlockNumber rs_blockno;		/* block where page will go */
+	bool all_visible;
+	bool		rs_buffer_valid;	/* T if any tuples in buffer */
+	bool		rs_logical_rewrite; /* do we need to do logical rewriting */
+	TransactionId rs_oldest_xmin;	/* oldest xmin used by caller to determine
+									 * tuple visibility */
+	TransactionId rs_freeze_xid;	/* Xid that will be used as freeze cutoff
+									 * point */
+	TransactionId rs_logical_xmin;	/* Xid that will be used as cutoff point
+									 * for logical rewrites */
+	MultiXactId rs_cutoff_multi;	/* MultiXactId that will be used as cutoff
+									 * point for multixacts */
+	MemoryContext rs_cxt;		/* for hash tables and entries and tuples in
+								 * them */
+	XLogRecPtr	rs_begin_lsn;	/* XLogInsertLsn when starting the rewrite */
+	HTAB	   *rs_unresolved_tups; /* unmatched A tuples */
+	HTAB	   *rs_old_new_tid_map; /* unmatched B tuples */
+	HTAB	   *rs_logical_mappings;	/* logical remapping files */
+	uint32		rs_num_rewrite_mappings;	/* # in memory mappings */
+}			RewriteStateData;
+
+
 extern RewriteState begin_heap_rewrite(Relation old_heap, Relation new_heap,
 									   TransactionId oldest_xmin, TransactionId freeze_xid,
 									   MultiXactId cutoff_multi);
-- 
2.37.2

v0-0002-VM-update-for-a-heap-block-not-in-SB.patchtext/x-patch; charset=US-ASCII; name=v0-0002-VM-update-for-a-heap-block-not-in-SB.patchDownload
From a80f1b3dbd9cc0bf060673fd860e0d59d2ca2159 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 3 Sep 2023 16:14:56 -0400
Subject: [PATCH v0 2/3] VM update for a heap block not in SB

WIP: needs to emit WAL, do error checking, and handle more conditions
than it currently does
---
 src/backend/access/heap/visibilitymap.c | 29 +++++++++++++++++++++++++
 src/include/access/visibilitymap.h      |  3 +++
 2 files changed, 32 insertions(+)

diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index 2e18cd88bc..adbc62dd7a 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -315,6 +315,35 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf,
 	LockBuffer(vmBuf, BUFFER_LOCK_UNLOCK);
 }
 
+/*
+ * TODO: emit WAL
+ */
+void
+visibilitymap_set_unbuffered(Relation rel, BlockNumber heap_block,
+		Buffer vm_buffer, uint8 flags)
+{
+	uint32		mapByte = HEAPBLK_TO_MAPBYTE(heap_block);
+	uint8		mapOffset = HEAPBLK_TO_OFFSET(heap_block);
+	Page		page;
+	uint8	   *map;
+
+	page = BufferGetPage(vm_buffer);
+	map = (uint8 *) PageGetContents(page);
+	LockBuffer(vm_buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	if (flags != (map[mapByte] >> mapOffset & VISIBILITYMAP_VALID_BITS))
+	{
+		START_CRIT_SECTION();
+
+		map[mapByte] |= (flags << mapOffset);
+		MarkBufferDirty(vm_buffer);
+
+		END_CRIT_SECTION();
+	}
+
+	LockBuffer(vm_buffer, BUFFER_LOCK_UNLOCK);
+}
+
 /*
  *	visibilitymap_get_status - get status of bits
  *
diff --git a/src/include/access/visibilitymap.h b/src/include/access/visibilitymap.h
index daaa01a257..83ca9b3b73 100644
--- a/src/include/access/visibilitymap.h
+++ b/src/include/access/visibilitymap.h
@@ -34,6 +34,9 @@ extern bool visibilitymap_pin_ok(BlockNumber heapBlk, Buffer vmbuf);
 extern void visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf,
 							  XLogRecPtr recptr, Buffer vmBuf, TransactionId cutoff_xid,
 							  uint8 flags);
+
+extern void
+visibilitymap_set_unbuffered(Relation rel, BlockNumber heap_block, Buffer vm_buffer, uint8 flags);
 extern uint8 visibilitymap_get_status(Relation rel, BlockNumber heapBlk, Buffer *vmbuf);
 extern void visibilitymap_count(Relation rel, BlockNumber *all_visible, BlockNumber *all_frozen);
 extern BlockNumber visibilitymap_prepare_truncate(Relation rel,
-- 
2.37.2