On Mon, Apr 19, 2021 at 8:04 PM Bharath Rupireddy
<bharath.rupireddyforpostg...@gmail.com> wrote:
>
> On Mon, Apr 19, 2021 at 1:57 PM Masahiko Sawada <sawada.m...@gmail.com> wrote:
> >
> > On Mon, Apr 19, 2021 at 5:04 PM Kyotaro Horiguchi
> > <horikyota....@gmail.com> wrote:
> > >
> > > At Mon, 19 Apr 2021 13:32:31 +0900, Masahiko Sawada 
> > > <sawada.m...@gmail.com> wrote in
> > > > On Fri, Apr 16, 2021 at 12:16 PM Kyotaro Horiguchi
> > > > <horikyota....@gmail.com> wrote:
> > > > > AFAICS the page is always empty when RelationGetBufferForTuple
> > > > > returned a valid vmbuffer.  So the "if" should be an "assert" instead.
> > > >
> > > > There is a chance that RelationGetBufferForTuple() returns a valid
> > > > vmbuffer but the page is not empty, since RelationGetBufferForTuple()
> > > > checks without a lock if the page is empty. But when it comes to
> > > > HEAP_INSERT_FROZEN cases it actually doesn’t happen at least for now
> > > > since only one process inserts tuples into the relation. Will fix.
> > >
> > > Yes.  It seems to me that it is cleaner that RelationGetBufferForTuple
> > > returns vmbuffer only when the caller needs to change vm state.
> > > Thanks.
> > >
> > > > > And, the patch changes the value of all_frozen_set to false when the
> > > > > page was already all-frozen (thus not empty). It would be fine since
> > > > > we don't need to change the visibility of the page in that case but
> > > > > the variable name is no longer correct.  set_all_visible or such?
> > > >
> > > > It seems to me that the variable name all_frozen_set corresponds to
> > > > XLH_INSERT_ALL_FROZEN_SET but I see your point. How about
> > > > set_all_frozen instead since we set all-frozen bits (also implying
> > > > setting all-visible)?
> > >
> > > Right. However, "if (set_all_frozen) then "set all_visible" looks like
> > > a bug^^;.  all_frozen_set looks better in that context than
> > > set_all_frozen. So I withdraw the comment.
> > >
> > > > BTW I found the following description of XLH_INSERT_ALL_FROZEN_SET but
> > > > there is no all_visible_set anywhere:
> > > >
> > > > /* all_frozen_set always implies all_visible_set */
> > > > #define XLH_INSERT_ALL_FROZEN_SET               (1<<5)
> > > >
> > > > I'll update those comments as well.
> > >
> > > FWIW, it seems like a shorthand of "ALL_FROZEN_SET implies ALL_VISIBLE
> > > to be set together". The current comment is working to me.
> > >
> >
> > Okay, I've updated the patch accordingly. Please review it.
>
> I was reading the patch, just found some typos: it should be "a frozen
> tuple" not "an frozen tuple".
>
> +     * Also pin visibility map page if we're inserting an frozen tuple into
> +                 * If we're inserting an frozen entry into empty page, pin 
> the

Thank you for the comment.

I’ve updated the patch including the above comment.


Regards,

--
Masahiko Sawada
EDB:  https://www.enterprisedb.com/
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 13396eb7f2..65ec6118b9 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2065,7 +2065,6 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 	Buffer		buffer;
 	Page		page = NULL;
 	Buffer		vmbuffer = InvalidBuffer;
-	bool		starting_with_empty_page;
 	bool		all_visible_cleared = false;
 	bool		all_frozen_set = false;
 	uint8		vmstatus = 0;
@@ -2082,8 +2081,8 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 	 * Find buffer to insert this tuple into.  If the page is all visible,
 	 * this will also pin the requisite visibility map page.
 	 *
-	 * Also pin visibility map page if COPY FREEZE inserts tuples into an
-	 * empty page. See all_frozen_set below.
+	 * Also pin visibility map page if we're inserting a frozen tuple into
+	 * an empty page. See all_frozen_set below.
 	 */
 	buffer = RelationGetBufferForTuple(relation, heaptup->t_len,
 									   InvalidBuffer, options, bistate,
@@ -2093,22 +2092,23 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 	/*
 	 * If we're inserting frozen entry into an empty page,
 	 * set visibility map bits and PageAllVisible() hint.
-	 *
-	 * If we're inserting frozen entry into already all_frozen page,
-	 * preserve this state.
 	 */
-	if (options & HEAP_INSERT_FROZEN)
+	if (options & HEAP_INSERT_FROZEN && BufferIsValid(vmbuffer))
 	{
-		page = BufferGetPage(buffer);
+		Assert(visibilitymap_pin_ok(BufferGetBlockNumber(buffer), vmbuffer));
 
-		starting_with_empty_page = PageGetMaxOffsetNumber(page) == 0;
+		page = BufferGetPage(buffer);
+		vmstatus = visibilitymap_get_status(relation,
+											BufferGetBlockNumber(buffer), &vmbuffer);
 
-		if (visibilitymap_pin_ok(BufferGetBlockNumber(buffer), vmbuffer))
-			vmstatus = visibilitymap_get_status(relation,
-								 BufferGetBlockNumber(buffer), &vmbuffer);
+		/* The page must be empty */
+		Assert(PageGetMaxOffsetNumber(page) == 0);
 
-		if ((starting_with_empty_page || vmstatus & VISIBILITYMAP_ALL_FROZEN))
-			all_frozen_set = true;
+		/*
+		 * If we're inserting frozen entry into empty page, we will set
+		 * all-visible to page and all-frozen on visibility map.
+		 */
+		all_frozen_set = true;
 	}
 
 	/*
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index ffc89685bf..420503725c 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -301,6 +301,13 @@ RelationAddExtraBlocks(Relation relation, BulkInsertState bistate)
  *	Note that in some cases the caller might have already acquired such pins,
  *	which is indicated by these arguments not being InvalidBuffer on entry.
  *
+ *	In HEAP_INSERT_FROZEN cases, we handle the possibility that the caller will
+ *	sets all-frozen bit on the visibility map page.  We pin on the visibility
+ *	map page so that the caller can set the bit later, only if the page is
+ *	empty.  If the page is all-visible, we skip getting a pin on the
+ *	visibility map page while assuming the caller will neither clear nor set
+ *	the bit on the page.
+ *
  *	We normally use FSM to help us find free space.  However,
  *	if HEAP_INSERT_SKIP_FSM is specified, we just append a new empty page to
  *	the end of the relation if the tuple won't fit on the current target page.
@@ -425,6 +432,8 @@ RelationGetBufferForTuple(Relation relation, Size len,
 loop:
 	while (targetBlock != InvalidBlockNumber)
 	{
+		bool skip_vmbuffer = false;
+
 		/*
 		 * Read and exclusive-lock the target block, as well as the other
 		 * block if one was given, taking suitable care with lock ordering and
@@ -442,14 +451,28 @@ loop:
 		{
 			/* easy case */
 			buffer = ReadBufferBI(relation, targetBlock, RBM_NORMAL, bistate);
-			if (PageIsAllVisible(BufferGetPage(buffer)))
-				visibilitymap_pin(relation, targetBlock, vmbuffer);
 
-			/*
-			 * If the page is empty, pin vmbuffer to set all_frozen bit later.
-			 */
-			if ((options & HEAP_INSERT_FROZEN) &&
-				(PageGetMaxOffsetNumber(BufferGetPage(buffer)) == 0))
+			if (options & HEAP_INSERT_FROZEN)
+			{
+				/*
+				 * If we're inserting a frozen entry into empty page, pin on the
+				 * visibility map buffer to set all_frozen bit later.  Note that
+				 * we check without a buffer lock if the page is empty but the
+				 * caller doesn't need to recheck that since we assume that in
+				 * HEAP_INSERT_FROZEN case, only one process is inserting a
+				 * frozen tuple into this relation.
+				 *
+				 * If the page already is non-empty and all-visible, we skip to
+				 * pin on a visibility map buffer since we never clear and set
+				 * all-frozen bit on visibility map during inserting a frozen
+				 * tuple.
+				 */
+				if (PageGetMaxOffsetNumber(BufferGetPage(buffer)) == 0)
+					visibilitymap_pin(relation, targetBlock, vmbuffer);
+				else if (PageIsAllVisible(BufferGetPage(buffer)))
+					skip_vmbuffer = true;
+			}
+			else if (PageIsAllVisible(BufferGetPage(buffer)))
 				visibilitymap_pin(relation, targetBlock, vmbuffer);
 
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -502,7 +525,13 @@ loop:
 		 * done a bit of extra work for no gain, but there's no real harm
 		 * done.
 		 */
-		if (otherBuffer == InvalidBuffer || targetBlock <= otherBlock)
+		if (skip_vmbuffer)
+		{
+			/* We must be inserting a frozen tuple into the all-visible page */
+			Assert(options & HEAP_INSERT_FROZEN);
+			Assert(PageIsAllVisible(BufferGetPage(buffer)));
+		}
+		else if (otherBuffer == InvalidBuffer || targetBlock <= otherBlock)
 			GetVisibilityMapPins(relation, buffer, otherBuffer,
 								 targetBlock, otherBlock, vmbuffer,
 								 vmbuffer_other);
@@ -517,6 +546,18 @@ loop:
 		 */
 		page = BufferGetPage(buffer);
 
+#ifdef USE_ASSERT_CHECKING
+		/*
+		 * If we pin on the visibility map when inserting a frozen tuple,
+		 * the page must still be empty.
+		 */
+		if ((options & HEAP_INSERT_FROZEN) && BufferIsValid(*vmbuffer))
+		{
+			Assert(PageGetMaxOffsetNumber(page) == 0);
+			Assert(!BufferIsValid(*vmbuffer_other));
+		}
+#endif
+
 		/*
 		 * If necessary initialize page, it'll be used soon.  We could avoid
 		 * dirtying the buffer here, and rely on the caller to do so whenever

Reply via email to