This patch adds the n_tup_newpage_upd to all the table stat views.

Just as we currently track HOT updates, it should be beneficial to track
updates where the new tuple cannot fit on the existing page and must go to
a different one.

Hopefully this can give users some insight as to whether their current
fillfactor settings need to be adjusted.

My chosen implementation replaces the hot-update boolean with an
update_type which is currently a three-value enum. I favored that
only slightly over adding a separate newpage-update boolean because the two
events are mutually exclusive and fewer parameters is less overhead and one
less assertion check. The relative wisdom of this choice may not come to
light until we add a new measurement and see whether that new measurement
overlaps either is-hot or is-new-page.
From 55204b3d2f719f5dd8c308ea722606a40b3d09b8 Mon Sep 17 00:00:00 2001
From: coreyhuinker <corey.huin...@gmail.com>
Date: Fri, 27 Jan 2023 17:56:59 -0500
Subject: [PATCH v1] Add n_tup_newpage_upd to pg_stat_all_tables and
 pg_stat_xact_all_tables. This value reflects the number of tuples updated
 where the new tuple was placed on a different page than the previous version.

---
 doc/src/sgml/monitoring.sgml                 |  9 +++++++++
 src/backend/access/heap/heapam.c             | 10 ++++++----
 src/backend/catalog/system_views.sql         |  4 +++-
 src/backend/utils/activity/pgstat_relation.c |  8 ++++++--
 src/backend/utils/adt/pgstatfuncs.c          | 18 ++++++++++++++++++
 src/include/catalog/pg_proc.dat              |  9 +++++++++
 src/include/pgstat.h                         | 14 ++++++++++++--
 src/test/regress/expected/rules.out          | 12 +++++++++---
 8 files changed, 72 insertions(+), 12 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 1756f1a4b6..f0291a21fb 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -4523,6 +4523,15 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>n_tup_newpage_upd</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of rows updated where the new row is on a different page than the previous version
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>n_live_tup</structfield> <type>bigint</type>
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index e6024a980b..f5aa429a9a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3155,7 +3155,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 				pagefree;
 	bool		have_tuple_lock = false;
 	bool		iscombo;
-	bool		use_hot_update = false;
+	PgStat_HeapUpdateType update_type = PGSTAT_HEAPUPDATE_NON_HOT;
+
 	bool		key_intact;
 	bool		all_visible_cleared = false;
 	bool		all_visible_cleared_new = false;
@@ -3838,10 +3839,11 @@ l2:
 		 * changed.
 		 */
 		if (!bms_overlap(modified_attrs, hot_attrs))
-			use_hot_update = true;
+			update_type = PGSTAT_HEAPUPDATE_HOT;
 	}
 	else
 	{
+		update_type = PGSTAT_HEAPUPDATE_NEW_PAGE;
 		/* Set a hint that the old page could use prune/defrag */
 		PageSetFull(page);
 	}
@@ -3875,7 +3877,7 @@ l2:
 	 */
 	PageSetPrunable(page, xid);
 
-	if (use_hot_update)
+	if (update_type == PGSTAT_HEAPUPDATE_HOT)
 	{
 		/* Mark the old tuple as HOT-updated */
 		HeapTupleSetHotUpdated(&oldtup);
@@ -3986,7 +3988,7 @@ l2:
 	if (have_tuple_lock)
 		UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
 
-	pgstat_count_heap_update(relation, use_hot_update);
+	pgstat_count_heap_update(relation, update_type);
 
 	/*
 	 * If heaptup is a private copy, release it.  Don't forget to copy t_self
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 8608e3fa5b..292a9b88b3 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -665,6 +665,7 @@ CREATE VIEW pg_stat_all_tables AS
             pg_stat_get_tuples_updated(C.oid) AS n_tup_upd,
             pg_stat_get_tuples_deleted(C.oid) AS n_tup_del,
             pg_stat_get_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_tuples_newpage_updated(C.oid) AS n_tup_newpage_upd,
             pg_stat_get_live_tuples(C.oid) AS n_live_tup,
             pg_stat_get_dead_tuples(C.oid) AS n_dead_tup,
             pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze,
@@ -696,7 +697,8 @@ CREATE VIEW pg_stat_xact_all_tables AS
             pg_stat_get_xact_tuples_inserted(C.oid) AS n_tup_ins,
             pg_stat_get_xact_tuples_updated(C.oid) AS n_tup_upd,
             pg_stat_get_xact_tuples_deleted(C.oid) AS n_tup_del,
-            pg_stat_get_xact_tuples_hot_updated(C.oid) AS n_tup_hot_upd
+            pg_stat_get_xact_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_xact_tuples_newpage_updated(C.oid) AS n_tup_newpage_upd
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 2e20b93c20..920ee71e35 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -362,7 +362,7 @@ pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
  * count a tuple update
  */
 void
-pgstat_count_heap_update(Relation rel, bool hot)
+pgstat_count_heap_update(Relation rel, PgStat_HeapUpdateType hut)
 {
 	if (pgstat_should_count_relation(rel))
 	{
@@ -372,8 +372,11 @@ pgstat_count_heap_update(Relation rel, bool hot)
 		pgstat_info->trans->tuples_updated++;
 
 		/* t_tuples_hot_updated is nontransactional, so just advance it */
-		if (hot)
+		if (hut == PGSTAT_HEAPUPDATE_HOT)
 			pgstat_info->t_counts.t_tuples_hot_updated++;
+		else if (hut == PGSTAT_HEAPUPDATE_NEW_PAGE)
+			pgstat_info->t_counts.t_tuples_newpage_updated++;
+
 	}
 }
 
@@ -793,6 +796,7 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	tabentry->tuples_updated += lstats->t_counts.t_tuples_updated;
 	tabentry->tuples_deleted += lstats->t_counts.t_tuples_deleted;
 	tabentry->tuples_hot_updated += lstats->t_counts.t_tuples_hot_updated;
+	tabentry->tuples_newpage_updated += lstats->t_counts.t_tuples_newpage_updated;
 
 	/*
 	 * If table was truncated/dropped, first reset the live/dead counters.
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 6737493402..ace9a46b6e 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -92,6 +92,9 @@ PG_STAT_GET_RELENTRY_INT64(tuples_fetched)
 /* pg_stat_get_tuples_hot_updated */
 PG_STAT_GET_RELENTRY_INT64(tuples_hot_updated)
 
+/* pg_stat_get_tuples_newpage_updated */
+PG_STAT_GET_RELENTRY_INT64(tuples_newpage_updated)
+
 /* pg_stat_get_tuples_inserted */
 PG_STAT_GET_RELENTRY_INT64(tuples_inserted)
 
@@ -1479,6 +1482,21 @@ pg_stat_get_xact_tuples_hot_updated(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_xact_tuples_newpage_updated(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_TableStatus *tabentry;
+
+	if ((tabentry = find_tabstat_entry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (tabentry->t_counts.t_tuples_newpage_updated);
+
+	PG_RETURN_INT64(result);
+}
+
 Datum
 pg_stat_get_xact_blocks_fetched(PG_FUNCTION_ARGS)
 {
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c0f2a8a77c..4a7ca3e146 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5337,6 +5337,10 @@
   proname => 'pg_stat_get_tuples_hot_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_tuples_hot_updated' },
+{ oid => '4642', descr => 'statistics: number of tuples non-hot newpage updated',
+  proname => 'pg_stat_get_tuples_newpage_updated', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tuples_newpage_updated' },
 { oid => '2878', descr => 'statistics: number of live tuples',
   proname => 'pg_stat_get_live_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
@@ -5791,6 +5795,11 @@
   proname => 'pg_stat_get_xact_tuples_hot_updated', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_tuples_hot_updated' },
+{ oid => '8704',
+  descr => 'statistics: number of tuples newpage updated in current transaction',
+  proname => 'pg_stat_get_xact_tuples_newpage_updated', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_xact_tuples_newpage_updated' },
 { oid => '3044',
   descr => 'statistics: number of blocks fetched in current transaction',
   proname => 'pg_stat_get_xact_blocks_fetched', provolatile => 'v',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 5e3326a3b9..4b21f8ffe7 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -81,6 +81,13 @@ typedef enum SessionEndType
 	DISCONNECT_KILLED
 } SessionEndType;
 
+typedef enum PgStat_HeapUpdateType
+{
+	PGSTAT_HEAPUPDATE_NON_HOT, /* regular update, same page */
+	PGSTAT_HEAPUPDATE_HOT, /* HOT update */
+	PGSTAT_HEAPUPDATE_NEW_PAGE /* regular update, new page */
+} PgStat_HeapUpdateType;
+
 /* ----------
  * The data type used for counters.
  * ----------
@@ -159,7 +166,7 @@ typedef struct PgStat_BackendSubEntry
  * the index AM, while tuples_fetched is the number of tuples successfully
  * fetched by heap_fetch under the control of simple indexscans for this index.
  *
- * tuples_inserted/updated/deleted/hot_updated count attempted actions,
+ * tuples_inserted/updated/deleted/hot_updated/newpage_updated count attempted actions,
  * regardless of whether the transaction committed.  delta_live_tuples,
  * delta_dead_tuples, and changed_tuples are set depending on commit or abort.
  * Note that delta_live_tuples and delta_dead_tuples can be negative!
@@ -176,6 +183,7 @@ typedef struct PgStat_TableCounts
 	PgStat_Counter t_tuples_updated;
 	PgStat_Counter t_tuples_deleted;
 	PgStat_Counter t_tuples_hot_updated;
+	PgStat_Counter t_tuples_newpage_updated;
 	bool		t_truncdropped;
 
 	PgStat_Counter t_delta_live_tuples;
@@ -363,6 +371,7 @@ typedef struct PgStat_StatTabEntry
 	PgStat_Counter tuples_updated;
 	PgStat_Counter tuples_deleted;
 	PgStat_Counter tuples_hot_updated;
+	PgStat_Counter tuples_newpage_updated;
 
 	PgStat_Counter live_tuples;
 	PgStat_Counter dead_tuples;
@@ -560,7 +569,8 @@ extern void pgstat_report_analyze(Relation rel,
 	} while (0)
 
 extern void pgstat_count_heap_insert(Relation rel, PgStat_Counter n);
-extern void pgstat_count_heap_update(Relation rel, bool hot);
+extern void pgstat_count_heap_update(Relation rel, PgStat_HeapUpdateType hut);
+
 extern void pgstat_count_heap_delete(Relation rel);
 extern void pgstat_count_truncate(Relation rel);
 extern void pgstat_update_heap_dead_tuples(Relation rel, int delta);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e7a2f5856a..bdd56d6283 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1789,6 +1789,7 @@ pg_stat_all_tables| SELECT c.oid AS relid,
     pg_stat_get_tuples_updated(c.oid) AS n_tup_upd,
     pg_stat_get_tuples_deleted(c.oid) AS n_tup_del,
     pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_tuples_newpage_updated(c.oid) AS n_tup_newpage_upd,
     pg_stat_get_live_tuples(c.oid) AS n_live_tup,
     pg_stat_get_dead_tuples(c.oid) AS n_dead_tup,
     pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze,
@@ -2134,6 +2135,7 @@ pg_stat_sys_tables| SELECT relid,
     n_tup_upd,
     n_tup_del,
     n_tup_hot_upd,
+    n_tup_newpage_upd,
     n_live_tup,
     n_dead_tup,
     n_mod_since_analyze,
@@ -2181,6 +2183,7 @@ pg_stat_user_tables| SELECT relid,
     n_tup_upd,
     n_tup_del,
     n_tup_hot_upd,
+    n_tup_newpage_upd,
     n_live_tup,
     n_dead_tup,
     n_mod_since_analyze,
@@ -2232,7 +2235,8 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
     pg_stat_get_xact_tuples_inserted(c.oid) AS n_tup_ins,
     pg_stat_get_xact_tuples_updated(c.oid) AS n_tup_upd,
     pg_stat_get_xact_tuples_deleted(c.oid) AS n_tup_del,
-    pg_stat_get_xact_tuples_hot_updated(c.oid) AS n_tup_hot_upd
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_xact_tuples_newpage_updated(c.oid) AS n_tup_newpage_upd
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -2248,7 +2252,8 @@ pg_stat_xact_sys_tables| SELECT relid,
     n_tup_ins,
     n_tup_upd,
     n_tup_del,
-    n_tup_hot_upd
+    n_tup_hot_upd,
+    n_tup_newpage_upd
    FROM pg_stat_xact_all_tables
   WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
@@ -2270,7 +2275,8 @@ pg_stat_xact_user_tables| SELECT relid,
     n_tup_ins,
     n_tup_upd,
     n_tup_del,
-    n_tup_hot_upd
+    n_tup_hot_upd,
+    n_tup_newpage_upd
    FROM pg_stat_xact_all_tables
   WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
-- 
2.39.1

Reply via email to