AdvanceXLInsertBuffer-code-review-v0.patch

text/plain

Filename: AdvanceXLInsertBuffer-code-review-v0.patch
Type: text/plain
Part: 0
Message: Re: Get rid of WALBufMappingLock

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 v0
File+
src/backend/access/transam/xlog.c 102 81
From: Noah Misch <noah@leadboat.com>

Cosmetic code review of AdvanceXLInsertBuffer().

This mostly changes material new in commit
bc22dc0e0ddc2dcb6043a732415019cc6b6bf683, but some of changes involve older
code.

==== Changes that avoid work -- move to separate patch(es)

Remove "upto = NewPageBeginPtr - 1;" so the "opportunistic" case doesn't
wait.

When the pg_atomic_compare_exchange_u64(&XLogCtl->InitializedEnd, ...)
loop advanced NewPageBeginPtr, we'd forget that progress for the next
iteration through the outer loop.  Use NewPageBeginPtr to control both
outer and inner loops, so we don't waste a compare-exchange in the outer
loop.

==== Code cosmetics

xlog.c has a standard that variables with names containing "End" refer
to the first byte position lacking the property in question.  Rename
some variables already having such last+1 semantics to include word
"End".

Prefer "ptr1 >= ptr2" to "ptr1 + XLOG_BLCKSZ > ptr" where both work.

Move variables to shorter-lived scope.  Rename variables.  Edit comments.

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 47ffc0a..dd832d1 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -468,37 +468,45 @@ typedef struct XLogCtlData
 	pg_atomic_uint64 logFlushResult;	/* last byte + 1 flushed */
 
 	/*
-	 * First initialized page in the cache (first byte position).
+	 * First byte of the first page containing post-recovery data.  (If one
+	 * page contains both pre-recovery and post-recovery data, it's that page.
+	 * This still points to the page's first byte, even though that particular
+	 * byte is pre-recovery.)  Does not change after startup.  Treat bytes
+	 * below this as infeasible to write out to pg_wal.
 	 */
-	XLogRecPtr	InitializedFrom;
+	XLogRecPtr	PreRecoveryPagesEnd;
 
 	/*
-	 * Latest reserved for initialization page in the cache (last byte
-	 * position + 1).
+	 * First byte that no ongoing AdvanceXLInsertBuffer() has reserved.
 	 *
-	 * To change the identity of a buffer, you need to advance
-	 * InitializeReserved first.  To change the identity of a buffer that's
-	 * still dirty, the old page needs to be written out first, and for that
-	 * you need WALWriteLock, and you need to ensure that there are no
-	 * in-progress insertions to the page by calling
+	 * Every byte greater than or equal to this has not started
+	 * initialization.  Before changing the xlblocks for a buffer (the
+	 * buffer's identity), you first need to advance InitializeReservedEnd to
+	 * a value >= the buffer's next future xlblocks value.  To change the
+	 * identity of a buffer that's still dirty, the old page needs to be
+	 * written out first, and for that you need WALWriteLock, and you need to
+	 * ensure that there are no in-progress insertions to the page by calling
 	 * WaitXLogInsertionsToFinish().
 	 */
-	pg_atomic_uint64 InitializeReserved;
+	pg_atomic_uint64 InitializeReservedEnd;
 
 	/*
-	 * Latest initialized page in the cache (last byte position + 1).
+	 * First byte in a possibly-uninitialized page.
 	 *
-	 * InitializedUpTo is updated after the buffer initialization.  After
-	 * update, waiters got notification using InitializedUpToCondVar.
+	 * Every byte less than this is in a page with both "xlblocks" and the
+	 * "pages" bytes initialized.  Bytes greater than or equal to this may or
+	 * may be in pages lacking one of those initializations, since updating
+	 * this is the last step of initialization.  After update, waiters get
+	 * notification from InitializedEndCondVar.
 	 */
-	pg_atomic_uint64 InitializedUpTo;
-	ConditionVariable InitializedUpToCondVar;
+	pg_atomic_uint64 InitializedEnd;
+	ConditionVariable InitializedEndCondVar;
 
 	/*
 	 * These values do not change after startup, although the pointed-to pages
 	 * and xlblocks values certainly do.  xlblocks values are changed
 	 * lock-free according to the check for the xlog write position and are
-	 * accompanied by changes of InitializeReserved and InitializedUpTo.
+	 * accompanied by changes of InitializeReservedEnd and InitializedEnd.
 	 */
 	char	   *pages;			/* buffers for unwritten XLOG pages */
 	pg_atomic_uint64 *xlblocks; /* 1st byte ptr-s + XLOG_BLCKSZ */
@@ -1996,40 +2004,44 @@ static void
 AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic)
 {
 	XLogCtlInsert *Insert = &XLogCtl->Insert;
-	int			nextidx;
-	XLogRecPtr	OldPageRqstPtr;
-	XLogwrtRqst WriteRqst;
-	XLogRecPtr	NewPageEndPtr = InvalidXLogRecPtr;
 	XLogRecPtr	NewPageBeginPtr;
-	XLogPageHeader NewPage;
-	XLogRecPtr	ReservedPtr;
 	int			npages pg_attribute_unused() = 0;
 
 	/*
-	 * We must run the loop below inside the critical section as we expect
-	 * XLogCtl->InitializedUpTo to eventually keep up.  The most of callers
-	 * already run inside the critical section. Except for WAL writer, which
-	 * passed 'opportunistic == true', and therefore we don't perform
-	 * operations that could error out.
-	 *
-	 * Start an explicit critical section anyway though.
+	 * Once we raise InitializeReservedEnd, we incur a duty to continue this
+	 * initialization work until we've updated xlblocks.  Non-opportunistic
+	 * callers already run inside a critical section.  Coincidentally, the
+	 * opportunistic caller (walwriter) prefers no pg_wal I/O here, so we
+	 * don't perform error-throwing operations for it.  Non-walwriter callers
+	 * accept PANIC on pg_wal I/O failure.
 	 */
 	Assert(CritSectionCount > 0 || opportunistic);
 	START_CRIT_SECTION();
 
 	/*--
-	 * Loop till we get all the pages in WAL buffer before 'upto' reserved for
-	 * initialization.  Multiple process can initialize different buffers with
-	 * this loop in parallel as following.
+	 * For !opportunistic callers, loop till we initialize all the pages in
+	 * WAL buffers before 'upto'.  For "opportunistic" caller walwriter, we
+	 * loop through all pages already written per the local LogwrtResult.Write
+	 * cache.  The idea is that walwriter writes out a batch of pages, then
+	 * initializes each future page that will occupy a buffer walwriter wrote
+	 * out.
 	 *
-	 * 1. Reserve page for initialization using XLogCtl->InitializeReserved.
+	 * Multiple process can initialize different buffers with this loop in
+	 * parallel as following.
+	 *
+	 * 1. Reserve page for initialization using XLogCtl->InitializeReservedEnd.
 	 * 2. Initialize the reserved page.
-	 * 3. Attempt to advance XLogCtl->InitializedUpTo,
+	 * 3. Attempt to advance XLogCtl->InitializedEnd,
 	 */
-	ReservedPtr = pg_atomic_read_u64(&XLogCtl->InitializeReserved);
-	while (upto >= ReservedPtr || opportunistic)
+	NewPageBeginPtr = pg_atomic_read_u64(&XLogCtl->InitializeReservedEnd);
+	while (upto >= NewPageBeginPtr || opportunistic)
 	{
-		Assert(ReservedPtr % XLOG_BLCKSZ == 0);
+		int			nextidx;
+		XLogRecPtr	OldPageEndPtr;
+		XLogPageHeader NewPage;
+		XLogRecPtr	NewPageEndPtr;
+
+		Assert(NewPageBeginPtr % XLOG_BLCKSZ == 0);
 
 		/*
 		 * Get ending-offset of the buffer page we need to replace.
@@ -2038,18 +2050,28 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic)
 		 * must wait to be written. If it was written, xlblocks will have this
 		 * position (or uninitialized)
 		 */
-		if (ReservedPtr + XLOG_BLCKSZ > XLogCtl->InitializedFrom + XLOG_BLCKSZ * XLOGbuffers)
-			OldPageRqstPtr = ReservedPtr + XLOG_BLCKSZ - (XLogRecPtr) XLOG_BLCKSZ * XLOGbuffers;
+		if (NewPageBeginPtr >= XLogCtl->PreRecoveryPagesEnd + XLOG_BLCKSZ * XLOGbuffers)
+		{
+			XLogRecPtr	OldPageBeginPtr = NewPageBeginPtr - (XLogRecPtr) XLOG_BLCKSZ * XLOGbuffers;
+
+			OldPageEndPtr = OldPageBeginPtr + XLOG_BLCKSZ;
+		}
 		else
-			OldPageRqstPtr = InvalidXLogRecPtr;
+		{
+			/*
+			 * Total WAL writes since recovery have been less than
+			 * wal_buffers.  There's no old page to replace.
+			 */
+			OldPageEndPtr = InvalidXLogRecPtr;
+		}
 
-		if (LogwrtResult.Write < OldPageRqstPtr && opportunistic)
+		if (LogwrtResult.Write < OldPageEndPtr && opportunistic)
 		{
 			/*
 			 * If we just want to pre-initialize as much as we can without
 			 * flushing, give up now.
 			 */
-			upto = ReservedPtr - 1;
+			Assert(upto == InvalidXLogRecPtr);
 			break;
 		}
 
@@ -2057,29 +2079,29 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic)
 		 * Attempt to reserve the page for initialization.  Failure means that
 		 * this page got reserved by another process.
 		 */
-		if (!pg_atomic_compare_exchange_u64(&XLogCtl->InitializeReserved,
-											&ReservedPtr,
-											ReservedPtr + XLOG_BLCKSZ))
+		if (!pg_atomic_compare_exchange_u64(&XLogCtl->InitializeReservedEnd,
+											&NewPageBeginPtr,
+											NewPageBeginPtr + XLOG_BLCKSZ))
 			continue;
 
 		/*
-		 * Wait till page gets correctly initialized up to OldPageRqstPtr.
+		 * Wait till page gets correctly initialized up to OldPageEndPtr.
 		 */
-		nextidx = XLogRecPtrToBufIdx(ReservedPtr);
-		while (pg_atomic_read_u64(&XLogCtl->InitializedUpTo) < OldPageRqstPtr)
-			ConditionVariableSleep(&XLogCtl->InitializedUpToCondVar, WAIT_EVENT_WAL_BUFFER_INIT);
+		nextidx = XLogRecPtrToBufIdx(NewPageBeginPtr);
+		while (pg_atomic_read_u64(&XLogCtl->InitializedEnd) < OldPageEndPtr)
+			ConditionVariableSleep(&XLogCtl->InitializedEndCondVar, WAIT_EVENT_WAL_BUFFER_INIT);
 		ConditionVariableCancelSleep();
-		Assert(pg_atomic_read_u64(&XLogCtl->xlblocks[nextidx]) == OldPageRqstPtr);
+		Assert(pg_atomic_read_u64(&XLogCtl->xlblocks[nextidx]) == OldPageEndPtr);
 
 		/* Fall through if it's already written out. */
-		if (LogwrtResult.Write < OldPageRqstPtr)
+		if (LogwrtResult.Write < OldPageEndPtr)
 		{
 			/* Nope, got work to do. */
 
 			/* Advance shared memory write request position */
 			SpinLockAcquire(&XLogCtl->info_lck);
-			if (XLogCtl->LogwrtRqst.Write < OldPageRqstPtr)
-				XLogCtl->LogwrtRqst.Write = OldPageRqstPtr;
+			if (XLogCtl->LogwrtRqst.Write < OldPageEndPtr)
+				XLogCtl->LogwrtRqst.Write = OldPageEndPtr;
 			SpinLockRelease(&XLogCtl->info_lck);
 
 			/*
@@ -2087,23 +2109,25 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic)
 			 * need to write it or if someone else already did.
 			 */
 			RefreshXLogWriteResult(LogwrtResult);
-			if (LogwrtResult.Write < OldPageRqstPtr)
+			if (LogwrtResult.Write < OldPageEndPtr)
 			{
-				WaitXLogInsertionsToFinish(OldPageRqstPtr);
+				WaitXLogInsertionsToFinish(OldPageEndPtr);
 
 				LWLockAcquire(WALWriteLock, LW_EXCLUSIVE);
 
 				RefreshXLogWriteResult(LogwrtResult);
-				if (LogwrtResult.Write >= OldPageRqstPtr)
+				if (LogwrtResult.Write >= OldPageEndPtr)
 				{
 					/* OK, someone wrote it already */
 					LWLockRelease(WALWriteLock);
 				}
 				else
 				{
+					XLogwrtRqst WriteRqst;
+
 					/* Have to write it ourselves */
 					TRACE_POSTGRESQL_WAL_BUFFER_WRITE_DIRTY_START();
-					WriteRqst.Write = OldPageRqstPtr;
+					WriteRqst.Write = OldPageEndPtr;
 					WriteRqst.Flush = 0;
 					XLogWrite(WriteRqst, tli, false);
 					LWLockRelease(WALWriteLock);
@@ -2117,8 +2141,6 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic)
 		 * Now the next buffer slot is free and we can set it up to be the
 		 * next output page.
 		 */
-		NewPageBeginPtr = ReservedPtr;
-		NewPageEndPtr = NewPageBeginPtr + XLOG_BLCKSZ;
 
 		NewPage = (XLogPageHeader) (XLogCtl->pages + nextidx * (Size) XLOG_BLCKSZ);
 
@@ -2178,14 +2200,13 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic)
 
 		/*
 		 * Make sure the initialization of the page becomes visible to others
-		 * before the xlblocks update. GetXLogBuffer() reads xlblocks without
-		 * holding a lock.
+		 * before the xlblocks update.
 		 */
 		pg_write_barrier();
 
 		/*-----
 		 * Update the value of XLogCtl->xlblocks[nextidx] and try to advance
-		 * XLogCtl->InitializedUpTo in a lock-less manner.
+		 * XLogCtl->InitializedEnd in a lock-less manner.
 		 *
 		 * First, let's provide a formal proof of the algorithm.  Let it be 'n'
 		 * process with the following variables in shared memory:
@@ -2228,21 +2249,22 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic)
 		 *    where v gets stuck.  Q.E.D.
 		 *
 		 * To apply this proof to the code below, we assume
-		 * XLogCtl->InitializedUpTo will play the role of v with XLOG_BLCKSZ
+		 * XLogCtl->InitializedEnd will play the role of v with XLOG_BLCKSZ
 		 * granularity.  We also assume setting XLogCtl->xlblocks[nextidx] to
 		 * NewPageEndPtr to play the role of setting f[i] to true.  Also, note
 		 * that processes can't concurrently map different xlog locations to
 		 * the same nextidx because we previously requested that
-		 * XLogCtl->InitializedUpTo >= OldPageRqstPtr.  So, a xlog buffer can
+		 * XLogCtl->InitializedEnd >= OldPageEndPtr.  So, a xlog buffer can
 		 * be taken for initialization only once the previous initialization
-		 * takes effect on XLogCtl->InitializedUpTo.
+		 * takes effect on XLogCtl->InitializedEnd.
 		 */
 
+		NewPageEndPtr = NewPageBeginPtr + XLOG_BLCKSZ;
 		pg_atomic_write_u64(&XLogCtl->xlblocks[nextidx], NewPageEndPtr);
 
 		pg_write_barrier();
 
-		while (pg_atomic_compare_exchange_u64(&XLogCtl->InitializedUpTo, &NewPageBeginPtr, NewPageEndPtr))
+		while (pg_atomic_compare_exchange_u64(&XLogCtl->InitializedEnd, &NewPageBeginPtr, NewPageEndPtr))
 		{
 			NewPageBeginPtr = NewPageEndPtr;
 			NewPageEndPtr = NewPageBeginPtr + XLOG_BLCKSZ;
@@ -2254,10 +2276,10 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic)
 			{
 				/*
 				 * Page at nextidx wasn't initialized yet, so we can't move
-				 * InitializedUpto further. It will be moved by backend which
+				 * InitializedEnd further. It will be moved by backend which
 				 * will initialize nextidx.
 				 */
-				ConditionVariableBroadcast(&XLogCtl->InitializedUpToCondVar);
+				ConditionVariableBroadcast(&XLogCtl->InitializedEndCondVar);
 				break;
 			}
 		}
@@ -2272,8 +2294,8 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic)
 	 * initialization.  However, some pages might be reserved by concurrent
 	 * processes.  Wait till they finish initialization.
 	 */
-	while (upto >= pg_atomic_read_u64(&XLogCtl->InitializedUpTo))
-		ConditionVariableSleep(&XLogCtl->InitializedUpToCondVar, WAIT_EVENT_WAL_BUFFER_INIT);
+	while (upto >= pg_atomic_read_u64(&XLogCtl->InitializedEnd))
+		ConditionVariableSleep(&XLogCtl->InitializedEndCondVar, WAIT_EVENT_WAL_BUFFER_INIT);
 	ConditionVariableCancelSleep();
 
 	pg_read_barrier();
@@ -5205,9 +5227,9 @@ XLOGShmemInit(void)
 	pg_atomic_init_u64(&XLogCtl->logFlushResult, InvalidXLogRecPtr);
 	pg_atomic_init_u64(&XLogCtl->unloggedLSN, InvalidXLogRecPtr);
 
-	pg_atomic_init_u64(&XLogCtl->InitializeReserved, InvalidXLogRecPtr);
-	pg_atomic_init_u64(&XLogCtl->InitializedUpTo, InvalidXLogRecPtr);
-	ConditionVariableInit(&XLogCtl->InitializedUpToCondVar);
+	pg_atomic_init_u64(&XLogCtl->InitializeReservedEnd, InvalidXLogRecPtr);
+	pg_atomic_init_u64(&XLogCtl->InitializedEnd, InvalidXLogRecPtr);
+	ConditionVariableInit(&XLogCtl->InitializedEndCondVar);
 }
 
 /*
@@ -6227,20 +6249,19 @@ StartupXLOG(void)
 		memset(page + len, 0, XLOG_BLCKSZ - len);
 
 		pg_atomic_write_u64(&XLogCtl->xlblocks[firstIdx], endOfRecoveryInfo->lastPageBeginPtr + XLOG_BLCKSZ);
-		pg_atomic_write_u64(&XLogCtl->InitializedUpTo, endOfRecoveryInfo->lastPageBeginPtr + XLOG_BLCKSZ);
-		XLogCtl->InitializedFrom = endOfRecoveryInfo->lastPageBeginPtr;
+		pg_atomic_write_u64(&XLogCtl->InitializedEnd, endOfRecoveryInfo->lastPageBeginPtr + XLOG_BLCKSZ);
+		XLogCtl->PreRecoveryPagesEnd = endOfRecoveryInfo->lastPageBeginPtr;
 	}
 	else
 	{
 		/*
-		 * There is no partial block to copy. Just set InitializedUpTo, and
-		 * let the first attempt to insert a log record to initialize the next
-		 * buffer.
+		 * There is no partial block to copy. Just set InitializedEnd, and let
+		 * the first log record initialize the next buffer.
 		 */
-		pg_atomic_write_u64(&XLogCtl->InitializedUpTo, EndOfLog);
-		XLogCtl->InitializedFrom = EndOfLog;
+		pg_atomic_write_u64(&XLogCtl->InitializedEnd, EndOfLog);
+		XLogCtl->PreRecoveryPagesEnd = EndOfLog;
 	}
-	pg_atomic_write_u64(&XLogCtl->InitializeReserved, pg_atomic_read_u64(&XLogCtl->InitializedUpTo));
+	pg_atomic_write_u64(&XLogCtl->InitializeReservedEnd, pg_atomic_read_u64(&XLogCtl->InitializedEnd));
 
 	/*
 	 * Update local and shared status.  This is OK to do without any locks