From 51786ef91cba43bbb4985dc8ab86f2cdcbf54369 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sat, 26 Aug 2023 20:16:00 -0400
Subject: [PATCH v1 1/3] Opportunistically freeze cold data when it is cheap

Instead of relying on whether or not pruning emitted an FPI while
vacuuming to determine whether or not to opportunistically freeze,
determine whether or not doing so will be cheap and useful. Determine if
it is cheap by checking if the buffer is already dirty and if freezing
the page will require emitting an FPI. Then, predict if the page will
stay frozen by comparing the page LSN to the insert LSN at the end of
the last vacuum of the relation. This gives us some idea of whether or
not the page has been recently modified, and, hopefully insight into
whether or not it will soon be modified again.
---
 src/backend/access/heap/vacuumlazy.c         | 26 ++++++++++++++----
 src/backend/storage/buffer/bufmgr.c          | 19 +++++++++++++
 src/backend/storage/buffer/localbuf.c        | 20 ++++++++++++++
 src/backend/utils/activity/pgstat_relation.c | 29 +++++++++++++++++++-
 src/include/pgstat.h                         |  8 +++++-
 src/include/storage/buf_internals.h          |  2 ++
 src/include/storage/bufmgr.h                 |  2 ++
 7 files changed, 98 insertions(+), 8 deletions(-)

diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 6a41ee635d..e4d5b402c2 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -210,6 +210,7 @@ typedef struct LVRelState
 	int64		live_tuples;	/* # live tuples remaining */
 	int64		recently_dead_tuples;	/* # dead, but not yet removable */
 	int64		missed_dead_tuples; /* # removable, but not removed */
+	XLogRecPtr last_vac_lsn;
 } LVRelState;
 
 /*
@@ -364,6 +365,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	errcallback.previous = error_context_stack;
 	error_context_stack = &errcallback;
 
+	vacrel->last_vac_lsn = pgstat_get_last_vac_lsn(RelationGetRelid(rel),
+			rel->rd_rel->relisshared);
+
 	/* Set up high level stuff about rel and its indexes */
 	vacrel->rel = rel;
 	vac_open_indexes(vacrel->rel, RowExclusiveLock, &vacrel->nindexes,
@@ -597,7 +601,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 						 rel->rd_rel->relisshared,
 						 Max(vacrel->new_live_tuples, 0),
 						 vacrel->recently_dead_tuples +
-						 vacrel->missed_dead_tuples);
+						 vacrel->missed_dead_tuples, ProcLastRecPtr);
+
 	pgstat_progress_end_command();
 
 	if (instrument)
@@ -1511,6 +1516,7 @@ lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, BlockNumber blkno,
 	return false;
 }
 
+#define FREEZE_CUTOFF_DIVISOR 3
 /*
  *	lazy_scan_prune() -- lazy_scan_heap() pruning and freezing.
  *
@@ -1551,9 +1557,11 @@ lazy_scan_prune(LVRelState *vacrel,
 				recently_dead_tuples;
 	int			nnewlpdead;
 	HeapPageFreeze pagefrz;
-	int64		fpi_before = pgWalUsage.wal_fpi;
 	OffsetNumber deadoffsets[MaxHeapTuplesPerPage];
 	HeapTupleFreeze frozen[MaxHeapTuplesPerPage];
+	XLogRecPtr current_lsn;
+	XLogRecPtr lsns_since_last_vacuum;
+	XLogRecPtr freeze_lsn_cutoff;
 
 	Assert(BufferGetBlockNumber(buf) == blkno);
 
@@ -1795,13 +1803,19 @@ retry:
 
 	/*
 	 * Freeze the page when heap_prepare_freeze_tuple indicates that at least
-	 * one XID/MXID from before FreezeLimit/MultiXactCutoff is present.  Also
-	 * freeze when pruning generated an FPI, if doing so means that we set the
-	 * page all-frozen afterwards (might not happen until final heap pass).
+	 * one XID/MXID from before FreezeLimit/MultiXactCutoff is present.  Also,
+	 * if freezing would allow us to set the whole page all-frozen, then freeze
+	 * if the buffer is already dirty, freezing won't emit an FPI and the page
+	 * hasn't been modified recently.
 	 */
+	current_lsn = GetXLogInsertRecPtr();
+	lsns_since_last_vacuum = current_lsn - vacrel->last_vac_lsn;
+	freeze_lsn_cutoff = current_lsn - (lsns_since_last_vacuum / FREEZE_CUTOFF_DIVISOR);
+
 	if (pagefrz.freeze_required || tuples_frozen == 0 ||
 		(prunestate->all_visible && prunestate->all_frozen &&
-		 fpi_before != pgWalUsage.wal_fpi))
+		(BufferIsProbablyDirty(buf) && !XLogCheckBufferNeedsBackup(buf) &&
+		PageGetLSN(page) < freeze_lsn_cutoff)))
 	{
 		/*
 		 * We're freezing the page.  Our final NewRelfrozenXid doesn't need to
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 3bd82dbfca..9110b686ae 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -2098,6 +2098,25 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 	return first_block;
 }
 
+bool
+BufferIsProbablyDirty(Buffer buffer)
+{
+	BufferDesc *bufHdr;
+	uint32		buf_state;
+
+	if (!BufferIsValid(buffer))
+		elog(ERROR, "bad buffer ID: %d", buffer);
+
+	if (BufferIsLocal(buffer))
+		return LocalBufferIsProbablyDirty(buffer);
+
+	bufHdr = GetBufferDescriptor(buffer - 1);
+
+	buf_state = pg_atomic_read_u32(&bufHdr->state);
+
+	return buf_state & BM_DIRTY;
+}
+
 /*
  * MarkBufferDirty
  *
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 1735ec7141..f2d4f91336 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -436,6 +436,26 @@ ExtendBufferedRelLocal(BufferManagerRelation bmr,
 	return first_block;
 }
 
+bool
+LocalBufferIsProbablyDirty(Buffer buffer)
+{
+	int			bufid;
+	BufferDesc *bufHdr;
+	uint32		buf_state;
+
+	Assert(BufferIsLocal(buffer));
+
+	bufid = -buffer - 1;
+
+	Assert(LocalRefCount[bufid] > 0);
+
+	bufHdr = GetLocalBufferDescriptor(bufid);
+
+	buf_state = pg_atomic_read_u32(&bufHdr->state);
+
+	return buf_state & BM_DIRTY;
+}
+
 /*
  * MarkLocalBufferDirty -
  *	  mark a local buffer dirty
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 9876e0c1e8..cac0a4bc49 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -205,12 +205,37 @@ pgstat_drop_relation(Relation rel)
 	}
 }
 
+
+XLogRecPtr
+pgstat_get_last_vac_lsn(Oid tableoid, bool shared)
+{
+	PgStat_EntryRef *entry_ref;
+	PgStatShared_Relation *shtabentry;
+	PgStat_StatTabEntry *tabentry;
+	XLogRecPtr result;
+	Oid			dboid = (shared ? InvalidOid : MyDatabaseId);
+
+	if (!pgstat_track_counts)
+		return InvalidXLogRecPtr;
+
+	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
+											dboid, tableoid, false);
+
+	shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
+	tabentry = &shtabentry->stats;
+
+	result = tabentry->last_vac_lsn;
+	pgstat_unlock_entry(entry_ref);
+	return result;
+}
+
 /*
  * Report that the table was just vacuumed and flush IO statistics.
  */
 void
 pgstat_report_vacuum(Oid tableoid, bool shared,
-					 PgStat_Counter livetuples, PgStat_Counter deadtuples)
+					 PgStat_Counter livetuples, PgStat_Counter deadtuples,
+					 XLogRecPtr last_vac_lsn)
 {
 	PgStat_EntryRef *entry_ref;
 	PgStatShared_Relation *shtabentry;
@@ -257,6 +282,8 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 		tabentry->vacuum_count++;
 	}
 
+	tabentry->last_vac_lsn = last_vac_lsn;
+
 	pgstat_unlock_entry(entry_ref);
 
 	/*
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 57a2c0866a..9fe607c961 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -11,6 +11,7 @@
 #ifndef PGSTAT_H
 #define PGSTAT_H
 
+#include "access/xlogdefs.h"
 #include "datatype/timestamp.h"
 #include "portability/instr_time.h"
 #include "postmaster/pgarch.h"	/* for MAX_XFN_CHARS */
@@ -424,6 +425,7 @@ typedef struct PgStat_StatTabEntry
 	PgStat_Counter analyze_count;
 	TimestampTz last_autoanalyze_time;	/* autovacuum initiated */
 	PgStat_Counter autoanalyze_count;
+	XLogRecPtr last_vac_lsn;
 } PgStat_StatTabEntry;
 
 typedef struct PgStat_WalStats
@@ -589,7 +591,11 @@ extern void pgstat_assoc_relation(Relation rel);
 extern void pgstat_unlink_relation(Relation rel);
 
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
-								 PgStat_Counter livetuples, PgStat_Counter deadtuples);
+								 PgStat_Counter livetuples, PgStat_Counter deadtuples,
+								 XLogRecPtr last_vac_lsn);
+
+extern XLogRecPtr pgstat_get_last_vac_lsn(Oid tableoid, bool shared);
+
 extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter);
diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index bc79a329a1..aaa7882099 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -430,6 +430,8 @@ extern BlockNumber ExtendBufferedRelLocal(BufferManagerRelation bmr,
 										  BlockNumber extend_upto,
 										  Buffer *buffers,
 										  uint32 *extended_by);
+
+extern bool LocalBufferIsProbablyDirty(Buffer buffer);
 extern void MarkLocalBufferDirty(Buffer buffer);
 extern void DropRelationLocalBuffers(RelFileLocator rlocator,
 									 ForkNumber forkNum,
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index b379c76e27..7458d5fd83 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -180,6 +180,8 @@ extern Buffer ReadBufferWithoutRelcache(RelFileLocator rlocator,
 extern void ReleaseBuffer(Buffer buffer);
 extern void UnlockReleaseBuffer(Buffer buffer);
 extern void MarkBufferDirty(Buffer buffer);
+
+extern bool BufferIsProbablyDirty(Buffer buffer);
 extern void IncrBufferRefCount(Buffer buffer);
 extern void CheckBufferIsPinnedOnce(Buffer buffer);
 extern Buffer ReleaseAndReadBuffer(Buffer buffer, Relation relation,
-- 
2.37.2

