From 1e8bf7042e1c652c490f9ccd2940d200617cbfee Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Mon, 5 Aug 2024 20:33:15 -0400
Subject: [PATCH v7 2/4] Add global LSNTimeStream to PgStat_WalStats

Add a globally maintained instance of an LSNTimeStream to
PgStat_WalStats and a utility function to insert new values.
The WAL generation rate is meant to be used for statistical purposes, so
it makes sense for it to live in the WAL stats data structure.

Background writer is tasked with inserting new LSN, time pairs to the
global stream in its main loop at regular intervals. There is precedent
for background writer performing such tasks: bgwriter already
periodically logs snapshots into the WAL for the benefit of standbys.
---
 src/backend/postmaster/bgwriter.c       | 81 +++++++++++++++++++++----
 src/backend/utils/activity/pgstat_wal.c | 15 +++++
 src/include/pgstat.h                    |  5 ++
 3 files changed, 89 insertions(+), 12 deletions(-)

diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 0f75548759a..bb5e2d8ec5d 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -76,6 +76,21 @@ int			BgWriterDelay = 200;
 static TimestampTz last_snapshot_ts;
 static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr;
 
+/*
+ * Interval at which new LSN, time pairs are added into the global
+ * LSNTimeStream, in milliseconds.
+ */
+#define LOG_STREAM_INTERVAL_MS 30000
+
+/*
+ * The timestamp at which we last checked whether or not to update the global
+ * LSNTimeStream.
+ */
+static TimestampTz last_stream_check_ts;
+
+/* The LSN we last updated the LSNTimeStream with */
+static XLogRecPtr last_stream_update_lsn = InvalidXLogRecPtr;
+
 
 /*
  * Main entry point for bgwriter process
@@ -119,6 +134,12 @@ BackgroundWriterMain(char *startup_data, size_t startup_data_len)
 	 */
 	last_snapshot_ts = GetCurrentTimestamp();
 
+	/* Insert an entry to the global LSNTimeStream as soon as we can. */
+	last_stream_check_ts = last_snapshot_ts;
+	last_stream_update_lsn = GetXLogInsertRecPtr();
+	pgstat_wal_update_lsntime_stream(last_stream_update_lsn,
+									 last_stream_check_ts);
+
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
 	 * that we can reset the context during error recovery and thereby avoid
@@ -269,26 +290,62 @@ BackgroundWriterMain(char *startup_data, size_t startup_data_len)
 		 * Checkpointer, when active, is barely ever in its mainloop and thus
 		 * makes it hard to log regularly.
 		 */
-		if (XLogStandbyInfoActive() && !RecoveryInProgress())
+
+		if (!RecoveryInProgress())
 		{
 			TimestampTz timeout = 0;
 			TimestampTz now = GetCurrentTimestamp();
 
-			timeout = TimestampTzPlusMilliseconds(last_snapshot_ts,
-												  LOG_SNAPSHOT_INTERVAL_MS);
+			if (XLogStandbyInfoActive())
+			{
+				timeout = TimestampTzPlusMilliseconds(last_snapshot_ts,
+													  LOG_SNAPSHOT_INTERVAL_MS);
+
+				/*
+				 * Only log if enough time has passed and interesting records
+				 * have been inserted since the last snapshot.  Have to
+				 * compare with <= instead of < because
+				 * GetLastImportantRecPtr() points at the start of a record,
+				 * whereas last_snapshot_lsn points just past the end of the
+				 * record.
+				 */
+				if (now >= timeout &&
+					last_snapshot_lsn <= GetLastImportantRecPtr())
+				{
+					last_snapshot_lsn = LogStandbySnapshot();
+					last_snapshot_ts = now;
+				}
+			}
+
+			timeout = TimestampTzPlusMilliseconds(last_stream_check_ts,
+												  LOG_STREAM_INTERVAL_MS);
 
 			/*
-			 * Only log if enough time has passed and interesting records have
-			 * been inserted since the last snapshot.  Have to compare with <=
-			 * instead of < because GetLastImportantRecPtr() points at the
-			 * start of a record, whereas last_snapshot_lsn points just past
-			 * the end of the record.
+			 * Periodically insert a new LSNTime into the global
+			 * LSNTimeStream. It makes sense for the background writer to
+			 * maintain the global LSNTimeStream because it runs regularly and
+			 * returns to its main loop frequently.
 			 */
-			if (now >= timeout &&
-				last_snapshot_lsn <= GetLastImportantRecPtr())
+			if (now >= timeout)
 			{
-				last_snapshot_lsn = LogStandbySnapshot();
-				last_snapshot_ts = now;
+				XLogRecPtr	insert_lsn = GetXLogInsertRecPtr();
+
+				Assert(insert_lsn != InvalidXLogRecPtr);
+
+				/*
+				 * We only insert an LSNTime if the LSN has changed since the
+				 * last update. This sacrifices accuracy on LSN -> time
+				 * conversions but saves space, which increases the accuracy
+				 * of time -> LSN conversions.
+				 */
+				if (insert_lsn > last_stream_update_lsn)
+				{
+					pgstat_wal_update_lsntime_stream(insert_lsn,
+													 now);
+					last_stream_update_lsn = insert_lsn;
+				}
+
+				last_stream_check_ts = now;
 			}
 		}
 
diff --git a/src/backend/utils/activity/pgstat_wal.c b/src/backend/utils/activity/pgstat_wal.c
index 95ec65a51ff..1ce9060641c 100644
--- a/src/backend/utils/activity/pgstat_wal.c
+++ b/src/backend/utils/activity/pgstat_wal.c
@@ -369,6 +369,21 @@ lsntime_insert(LSNTimeStream *stream, TimestampTz time,
 }
 
 
+/*
+ * Utility function for inserting a new member into the LSNTimeStream member
+ * of WAL stats.
+ */
+void
+pgstat_wal_update_lsntime_stream(XLogRecPtr lsn, TimestampTz time)
+{
+	PgStatShared_Wal *stats_shmem = &pgStatLocal.shmem->wal;
+
+	LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE);
+	lsntime_insert(&stats_shmem->stats.stream, time, lsn);
+	LWLockRelease(&stats_shmem->lock);
+}
+
+
 /*
  * Returns a range of LSNTimes starting at lower and ending at upper and
  * covering the target_time. If target_time is before the stream, lower will
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 13856e2bef3..43df60ce24c 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -507,6 +507,7 @@ typedef struct PgStat_WalStats
 	PgStat_Counter wal_sync;
 	PgStat_Counter wal_write_time;
 	PgStat_Counter wal_sync_time;
+	LSNTimeStream stream;
 	TimestampTz stat_reset_timestamp;
 } PgStat_WalStats;
 
@@ -795,6 +796,10 @@ extern void time_bounds_for_lsn(const LSNTimeStream *stream,
 								XLogRecPtr target_lsn,
 								LSNTime *lower, LSNTime *upper);
 
+/* Helper for maintaining the global LSNTimeStream */
+extern void pgstat_wal_update_lsntime_stream(XLogRecPtr lsn,
+											 TimestampTz time);
+
 
 /*
  * Variables in pgstat.c
-- 
2.34.1

