cleaning up a few CLOG-related things

Started by Robert Haasalmost 5 years ago4 messages
#1Robert Haas
robertmhaas@gmail.com
4 attachment(s)

I attach a series of proposed patches to slightly improve some minor
things related to the CLOG code.

0001 - Always call StartupCLOG() just after initializing
ShmemVariableCache. Right now, the hot_standby=off code path does this
at end of recovery, and the hot_standby=on code path does it at the
beginning of recovery. It's better to do this in only one place
because (a) it's simpler, (b) StartupCLOG() is trivial so trying to
postpone it in the hot_standby=off case has no value, and (c) it
allows for 0002 and therefore 0003, which make things even simpler.

0002 - In clog_redo(), don't set XactCtl->shared->latest_page_number.
The value that is being set here is actually the oldest page we're not
truncating, not the newest page that exists, so it's a bogus value
(except when we're truncating all but one page). The reason it's like
this now is to avoid having SimpleLruTruncate() see an uninitialized
value that might trip a sanity check, but after 0001 that won't be
possible, so we can just let the sanity check do its thing.

0003 - In TrimCLOG(), don't reset XactCtl->shared->latest_page_number.
After we stop doing 0002 we don't need to do this either, because the
only thing this can be doing for us is correcting the error introduced
by the code which 0002 removes. Relying on the results of replaying
(authoritative) CLOG/EXTEND records seems better than relying on our
(approximate) value of nextXid at end of recovery.

0004 - In StartupCLOG(), correct an off-by-one error. Currently, if
the nextXid is exactly a multiple of the number of CLOG entries that
fit on a page, then the value we compute for
XactCtl->shared->latest_page_number is higher than it should be by 1.
That's because nextXid represents the first XID that hasn't yet been
allocated, not the last one that gets allocated. So, the CLOG page
gets created when nextXid advances from the first value on the page to
the second value on the page, not when it advances from the last value
on the previous page to the first value on the current page.

Note that all of 0001-0003 result in a net removal of code. 0001 comes
out to more lines total because of the comment changes, but fewer
executable lines.

I don't plan to back-patch any of this because, AFAICS, an incorrect
value of XactCtl->shared->latest_page_number has no real consequences.
The SLRU code uses latest_page_number for just two purposes. First,
the latest page is never evicted; but that's just a question of
performance, not correctness, and the performance impact is small.
Second, the sanity check in SimpleLruTruncate() uses it. The present
code can make the value substantially inaccurate during recovery, but
only in a way that can make the sanity check pass rather than failing,
so it's not going to really bite anybody except perhaps if they have a
corrupted cluster where they would have liked the sanity check to
catch some problem. When not in recovery, the value can be off by at
most one. I am not sure whether there's a theoretical risk of this
making SimpleLruTruncate()'s sanity check fail when it should have
passed, but even if there is the chances must be extremely remote.

Some of the other SLRUs have similar issues as a result of
copy-and-paste work over the years. I plan to look at tidying that
stuff up, too. However, I wanted to post (and probably commit) these
patches first, partly to get some feedback, and also because all the
cases are a little different and I want to make sure to do a proper
analysis of each one.

Any review very much appreciated.

Thanks,

--
Robert Haas
EDB: http://www.enterprisedb.com

Attachments:

v1-0003-In-TrimCLOG-don-t-reset-XactCtl-shared-latest_pag.patchapplication/octet-stream; name=v1-0003-In-TrimCLOG-don-t-reset-XactCtl-shared-latest_pag.patchDownload
From aebab6ae8726b968b63e2079f84d751e5341fb4f Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 25 Jan 2021 11:18:44 -0500
Subject: [PATCH v1 3/4] In TrimCLOG(), don't reset
 XactCtl->shared->latest_page_number.

Since the CLOG page number is not recorded directly in the checkpoint
record, we have to use ShmemVariableCache->nextXid to figure out the
latest CLOG page number at the start of recovery. However, as recovery
progresses, replay of CLOG/EXTEND records will update our notion of
the latest page number, and we should rely on that being accurate
rather than recomputing the value based on an updated notion of
nextXid. ShmemVariableCache->nextXid is only an approximation
during recovery anyway, whereas CLOG/EXTEND records are an
authoritative representation of how the SLRU has been updated.

Commit [ INSERT COMMIT ID FOR 0002 HERE ] makes this
simplification possible, as before that change clog_redo() might
have injected a bogus value here, and we'd want to get rid of
that before entering normal running.
---
 src/backend/access/transam/clog.c | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 5ae962b86e..6fa4713fb4 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -773,11 +773,6 @@ TrimCLOG(void)
 
 	LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
 
-	/*
-	 * Re-Initialize our idea of the latest page number.
-	 */
-	XactCtl->shared->latest_page_number = pageno;
-
 	/*
 	 * Zero out the remainder of the current clog page.  Under normal
 	 * circumstances it should be zeroes already, but it seems at least
-- 
2.24.3 (Apple Git-128)

v1-0002-In-clog_redo-don-t-set-XactCtl-shared-latest_page.patchapplication/octet-stream; name=v1-0002-In-clog_redo-don-t-set-XactCtl-shared-latest_page.patchDownload
From c3fff35ea0c5b243a2cc19e3408efd020c31dd12 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 25 Jan 2021 11:08:08 -0500
Subject: [PATCH v1 2/4] In clog_redo(), don't set
 XactCtl->shared->latest_page_number.

The comment is no longer accurate, and hasn't been entirely accurate
since Hot Standby was introduced. The original idea here was that
StartupCLOG() wouldn't be called until the end of recovery and
therefore this value would be uninitialized when this code is
reached, but Hot Standby made that true only when hot_standby=off,
and commit [ INSERT COMMIT ID FOR 0001 HERE ] means
that this value is now always initialized before replay even starts.

The original purpose of this code was to bypass the sanity check
in SimpleLruTruncate(), which will no longer occur: now, if something
is wrong, that sanity check might trip during recovery. That's
probably a good thing, because in the current code base
latest_page_number should always be initialized and therefore we
expect that the sanity check should pass. If it doesn't, something
has gone wrong, and complaining about it is appropriate.
---
 src/backend/access/transam/clog.c | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 410d02a394..5ae962b86e 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -1011,12 +1011,6 @@ clog_redo(XLogReaderState *record)
 
 		memcpy(&xlrec, XLogRecGetData(record), sizeof(xl_clog_truncate));
 
-		/*
-		 * During XLOG replay, latest_page_number isn't set up yet; insert a
-		 * suitable value to bypass the sanity test in SimpleLruTruncate.
-		 */
-		XactCtl->shared->latest_page_number = xlrec.pageno;
-
 		AdvanceOldestClogXid(xlrec.oldestXact);
 
 		SimpleLruTruncate(XactCtl, xlrec.pageno);
-- 
2.24.3 (Apple Git-128)

v1-0004-In-StartupCLOG-correct-an-off-by-one-error.patchapplication/octet-stream; name=v1-0004-In-StartupCLOG-correct-an-off-by-one-error.patchDownload
From addfc07b80e304194129423eabe78a4608335220 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 25 Jan 2021 11:32:11 -0500
Subject: [PATCH v1 4/4] In StartupCLOG(), correct an off-by-one error.

nextXid represents the first XID that hasn't yet been allocated, not
the last one that gets allocated. So, we should use the previous XID
to determine the last CLOG page that ought to exist.

As far as we currently know, this mistake doesn't have any adverse
consequences at present, but it seems better to make the logic
more correct.
---
 src/backend/access/transam/clog.c | 27 ++++++++++++++++++++-------
 1 file changed, 20 insertions(+), 7 deletions(-)

diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 6fa4713fb4..bad883d5a2 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -742,6 +742,23 @@ ZeroCLOGPage(int pageno, bool writeXlog)
 	return slotno;
 }
 
+/*
+ * Based on ShmemVariableCache->nextXid, compute the latest CLOG page that
+ * is expected to exist.
+ */
+static int
+FullTransactionIdToLatestPageNumber(FullTransactionId nextXid)
+{
+	/*
+	 * If nextXid coresponds to the first transaction on a CLOG page, then
+	 * the CLOG page will not have been initialized yet, but if it corresponds
+	 * to the second or later transaction on the page, then it should already
+	 * exist. See GetNewTransactionId().
+	 */
+	FullTransactionIdRetreat(&nextXid);
+	return TransactionIdToPage(XidFromFullTransactionId(nextXid));
+}
+
 /*
  * This must be called ONCE during postmaster or standalone-backend startup,
  * after StartupXLOG has initialized ShmemVariableCache->nextXid.
@@ -749,16 +766,12 @@ ZeroCLOGPage(int pageno, bool writeXlog)
 void
 StartupCLOG(void)
 {
-	TransactionId xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
-	int			pageno = TransactionIdToPage(xid);
+	int			pageno;
 
+	/* Initialize our idea of the latest page number. */
+	pageno = FullTransactionIdToLatestPageNumber(ShmemVariableCache->nextXid);
 	LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
-
-	/*
-	 * Initialize our idea of the latest page number.
-	 */
 	XactCtl->shared->latest_page_number = pageno;
-
 	LWLockRelease(XactSLRULock);
 }
 
-- 
2.24.3 (Apple Git-128)

v1-0001-Move-StartupCLOG-calls-to-just-after-we-initializ.patchapplication/octet-stream; name=v1-0001-Move-StartupCLOG-calls-to-just-after-we-initializ.patchDownload
From cb14fe3a02fe3db03e46d0371ac6cc8a2b113576 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 25 Jan 2021 11:04:22 -0500
Subject: [PATCH v1 1/4] Move StartupCLOG() calls to just after we initialize
 ShmemVariableCache.

Previously, the hot_standby=off code path did this at end of recovery,
while the hot_standby=on code path did it at the beginning of recovery.
It's better to do this in only one place because (a) it's simpler,
(b) StartupCLOG() is trivial so trying to postpone the work isn't
useful, and (c) this will make it possible to simplify some other
logic.
---
 src/backend/access/transam/xlog.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 470e113b33..1cfee5c270 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6859,6 +6859,13 @@ StartupXLOG(void)
 	 */
 	StartupReorderBuffer();
 
+	/*
+	 * Startup CLOG. This must be done after ShmemVariableCache->nextXid
+	 * has been initialized and before we accept connections or begin WAL
+	 * replay.
+	 */
+	StartupCLOG();
+
 	/*
 	 * Startup MultiXact. We need to do this early to be able to replay
 	 * truncations.
@@ -7129,11 +7136,10 @@ StartupXLOG(void)
 			ProcArrayInitRecovery(XidFromFullTransactionId(ShmemVariableCache->nextXid));
 
 			/*
-			 * Startup commit log and subtrans only.  MultiXact and commit
+			 * Startup subtrans only.  CLOG, MultiXact and commit
 			 * timestamp have already been started up and other SLRUs are not
 			 * maintained during recovery and need not be started yet.
 			 */
-			StartupCLOG();
 			StartupSUBTRANS(oldestActiveXID);
 
 			/*
@@ -7949,14 +7955,11 @@ StartupXLOG(void)
 	LWLockRelease(ProcArrayLock);
 
 	/*
-	 * Start up the commit log and subtrans, if not already done for hot
-	 * standby.  (commit timestamps are started below, if necessary.)
+	 * Start up subtrans, if not already done for hot standby.  (commit
+	 * timestamps are started below, if necessary.)
 	 */
 	if (standbyState == STANDBY_DISABLED)
-	{
-		StartupCLOG();
 		StartupSUBTRANS(oldestActiveXID);
-	}
 
 	/*
 	 * Perform end of recovery actions for any SLRUs that need it.
-- 
2.24.3 (Apple Git-128)

#2Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Robert Haas (#1)
Re: cleaning up a few CLOG-related things

On 25/01/2021 18:56, Robert Haas wrote:

I attach a series of proposed patches to slightly improve some minor
things related to the CLOG code.

[patches 0001 - 0003]

Makes sense.

0004 - In StartupCLOG(), correct an off-by-one error. Currently, if
the nextXid is exactly a multiple of the number of CLOG entries that
fit on a page, then the value we compute for
XactCtl->shared->latest_page_number is higher than it should be by 1.
That's because nextXid represents the first XID that hasn't yet been
allocated, not the last one that gets allocated.

Yes.

So, the CLOG page gets created when nextXid advances from the first
value on the page to the second value on the page, not when it
advances from the last value on the previous page to the first value
on the current page.

Yes. It took me a moment to understand that explanation, though. I'd
phrase it something like "nextXid is the next XID that will be used, but
we want to set latest_page_number according to the last XID that's
already been used. So retreat by one."

Having a separate FullTransactionIdToLatestPageNumber() function for
this seems like overkill to me.

Some of the other SLRUs have similar issues as a result of
copy-and-paste work over the years. I plan to look at tidying that
stuff up, too. However, I wanted to post (and probably commit) these
patches first, partly to get some feedback, and also because all the
cases are a little different and I want to make sure to do a proper
analysis of each one.

Yeah, multixact seems similar at least.

- Heikki

#3Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#2)
3 attachment(s)
Re: cleaning up a few CLOG-related things

On Mon, Jan 25, 2021 at 2:11 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

[patches 0001 - 0003]

Makes sense.

Great. I committed the first one and will proceed with those as well.

So, the CLOG page gets created when nextXid advances from the first
value on the page to the second value on the page, not when it
advances from the last value on the previous page to the first value
on the current page.

Yes. It took me a moment to understand that explanation, though. I'd
phrase it something like "nextXid is the next XID that will be used, but
we want to set latest_page_number according to the last XID that's
already been used. So retreat by one."

OK, updated the patch to use that language for the comment.

Having a separate FullTransactionIdToLatestPageNumber() function for
this seems like overkill to me.

I initially thought so too, but it turned out to be pretty useful for
writing debugging cross-checks and things, so I'm inclined to keep it
even though I'm not at present proposing to commit any such debugging
cross-checks. For example I tried making the main redo loop check
whether XactCtl->shared->latest_page_number ==
FullTransactionIdToLatestPageNumber(nextXid) which turned out to be
super-helpful in understanding things.

--
Robert Haas
EDB: http://www.enterprisedb.com

Attachments:

v2-0001-In-clog_redo-don-t-set-XactCtl-shared-latest_page.patchapplication/octet-stream; name=v2-0001-In-clog_redo-don-t-set-XactCtl-shared-latest_page.patchDownload
From 7bff2d35c32698f2dee35c9bbbba69de51ebaca4 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 25 Jan 2021 11:08:08 -0500
Subject: [PATCH v2 1/3] In clog_redo(), don't set
 XactCtl->shared->latest_page_number.

The comment is no longer accurate, and hasn't been entirely accurate
since Hot Standby was introduced. The original idea here was that
StartupCLOG() wouldn't be called until the end of recovery and
therefore this value would be uninitialized when this code is reached,
but Hot Standby made that true only when hot_standby=off, and commit
1f113abdf87cd085dee3927960bb4f70442b7250 means that this value is now
always initialized before replay even starts.

The original purpose of this code was to bypass the sanity check
in SimpleLruTruncate(), which will no longer occur: now, if something
is wrong, that sanity check might trip during recovery. That's
probably a good thing, because in the current code base
latest_page_number should always be initialized and therefore we
expect that the sanity check should pass. If it doesn't, something
has gone wrong, and complaining about it is appropriate.

Patch by me, reviewed by Heikki Linnakangas.

Discussion: http://postgr.es/m/CA+TgmoZYig9+AQodhF5sRXuKkJ=RgFDugLr3XX_dz_F-p=TwTg@mail.gmail.com
---
 src/backend/access/transam/clog.c | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 410d02a394..5ae962b86e 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -1011,12 +1011,6 @@ clog_redo(XLogReaderState *record)
 
 		memcpy(&xlrec, XLogRecGetData(record), sizeof(xl_clog_truncate));
 
-		/*
-		 * During XLOG replay, latest_page_number isn't set up yet; insert a
-		 * suitable value to bypass the sanity test in SimpleLruTruncate.
-		 */
-		XactCtl->shared->latest_page_number = xlrec.pageno;
-
 		AdvanceOldestClogXid(xlrec.oldestXact);
 
 		SimpleLruTruncate(XactCtl, xlrec.pageno);
-- 
2.24.3 (Apple Git-128)

v2-0002-In-TrimCLOG-don-t-reset-XactCtl-shared-latest_pag.patchapplication/octet-stream; name=v2-0002-In-TrimCLOG-don-t-reset-XactCtl-shared-latest_pag.patchDownload
From ba2c7fa75c224efadf315fdd3db5d3f61cc94dd8 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 25 Jan 2021 11:18:44 -0500
Subject: [PATCH v2 2/3] In TrimCLOG(), don't reset
 XactCtl->shared->latest_page_number.

Since the CLOG page number is not recorded directly in the checkpoint
record, we have to use ShmemVariableCache->nextXid to figure out the
latest CLOG page number at the start of recovery. However, as recovery
progresses, replay of CLOG/EXTEND records will update our notion of
the latest page number, and we should rely on that being accurate
rather than recomputing the value based on an updated notion of
nextXid. ShmemVariableCache->nextXid is only an approximation
during recovery anyway, whereas CLOG/EXTEND records are an
authoritative representation of how the SLRU has been updated.

Commit [ INSERT COMMIT ID FOR 0002 HERE ] makes this
simplification possible, as before that change clog_redo() might
have injected a bogus value here, and we'd want to get rid of
that before entering normal running.
---
 src/backend/access/transam/clog.c | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 5ae962b86e..6fa4713fb4 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -773,11 +773,6 @@ TrimCLOG(void)
 
 	LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
 
-	/*
-	 * Re-Initialize our idea of the latest page number.
-	 */
-	XactCtl->shared->latest_page_number = pageno;
-
 	/*
 	 * Zero out the remainder of the current clog page.  Under normal
 	 * circumstances it should be zeroes already, but it seems at least
-- 
2.24.3 (Apple Git-128)

v2-0003-In-StartupCLOG-correct-an-off-by-one-error.patchapplication/octet-stream; name=v2-0003-In-StartupCLOG-correct-an-off-by-one-error.patchDownload
From 2297775aedbc5f2e47083c4dbebdb9f74bdd8bf1 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 27 Jan 2021 12:28:37 -0500
Subject: [PATCH v2 3/3] In StartupCLOG(), correct an off-by-one error.

nextXid represents the first XID that hasn't yet been allocated, not
the last one that gets allocated. So, we should use the previous XID
to determine the last CLOG page that ought to exist.

As far as we currently know, this mistake doesn't have any adverse
consequences at present, but it seems better to make the logic
more correct.

Patch by me, reviewed by Heikki Linnakangas.

Discussion: http://postgr.es/m/CA+TgmoZYig9+AQodhF5sRXuKkJ=RgFDugLr3XX_dz_F-p=TwTg@mail.gmail.com
---
 src/backend/access/transam/clog.c | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 6fa4713fb4..560eb48e8f 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -742,6 +742,22 @@ ZeroCLOGPage(int pageno, bool writeXlog)
 	return slotno;
 }
 
+/*
+ * Based on ShmemVariableCache->nextXid, compute the latest CLOG page that
+ * is expected to exist.
+ */
+static int
+FullTransactionIdToLatestPageNumber(FullTransactionId nextXid)
+{
+	/*
+	 * nextXid is the next XID that will be used, but we want to set
+	 * latest_page_number according to the last XID that's already been used.
+	 * So retreat by one. See also GetNewTransactionId().
+	 */
+	FullTransactionIdRetreat(&nextXid);
+	return TransactionIdToPage(XidFromFullTransactionId(nextXid));
+}
+
 /*
  * This must be called ONCE during postmaster or standalone-backend startup,
  * after StartupXLOG has initialized ShmemVariableCache->nextXid.
@@ -749,16 +765,12 @@ ZeroCLOGPage(int pageno, bool writeXlog)
 void
 StartupCLOG(void)
 {
-	TransactionId xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
-	int			pageno = TransactionIdToPage(xid);
+	int			pageno;
 
+	/* Initialize our idea of the latest page number. */
+	pageno = FullTransactionIdToLatestPageNumber(ShmemVariableCache->nextXid);
 	LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
-
-	/*
-	 * Initialize our idea of the latest page number.
-	 */
 	XactCtl->shared->latest_page_number = pageno;
-
 	LWLockRelease(XactSLRULock);
 }
 
-- 
2.24.3 (Apple Git-128)

#4Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#3)
Re: cleaning up a few CLOG-related things

On Wed, Jan 27, 2021 at 12:35:30PM -0500, Robert Haas wrote:

On Mon, Jan 25, 2021 at 2:11 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Having a separate FullTransactionIdToLatestPageNumber() function for
this seems like overkill to me.

I initially thought so too, but it turned out to be pretty useful for
writing debugging cross-checks and things, so I'm inclined to keep it
even though I'm not at present proposing to commit any such debugging
cross-checks. For example I tried making the main redo loop check
whether XactCtl->shared->latest_page_number ==
FullTransactionIdToLatestPageNumber(nextXid) which turned out to be
super-helpful in understanding things.

+/*
+ * Based on ShmemVariableCache->nextXid, compute the latest CLOG page that
+ * is expected to exist.
+ */
+static int
+FullTransactionIdToLatestPageNumber(FullTransactionId nextXid)
+{
+	/*
+	 * nextXid is the next XID that will be used, but we want to set
+	 * latest_page_number according to the last XID that's already been used.
+	 * So retreat by one. See also GetNewTransactionId().
+	 */
+	FullTransactionIdRetreat(&nextXid);
+	return TransactionIdToPage(XidFromFullTransactionId(nextXid));
+}

I don't mind the arguably-overkill function. I'd probably have named it
FullTransactionIdPredecessorToPage(), to focus on its behavior as opposed to
its caller's behavior, but static function naming isn't a weighty matter.
Overall, the patch looks fine. If nextXid is the first on a page, the next
GetNewTransactionId() -> ExtendCLOG() -> ZeroCLOGPage() -> SimpleLruZeroPage()
is responsible for updating latest_page_number.