Count updates that move row to a new page.
authorPeter Geoghegan <pg@bowt.ie>
Thu, 23 Mar 2023 18:16:17 +0000 (11:16 -0700)
committerPeter Geoghegan <pg@bowt.ie>
Thu, 23 Mar 2023 18:16:17 +0000 (11:16 -0700)
Add pgstat counter to track row updates that result in the successor
version going to a new heap page, leaving behind an original version
whose t_ctid points to the new version.  The current count is shown by
the n_tup_newpage_upd column of each of the pg_stat_*_tables views.

The new n_tup_newpage_upd column complements the existing n_tup_hot_upd
and n_tup_upd columns.  Tables that have high n_tup_newpage_upd values
(relative to n_tup_upd) are good candidates for tuning heap fillfactor.

Corey Huinker, with small tweaks by me.

Author: Corey Huinker <corey.huinker@gmail.com>
Reviewed-By: Peter Geoghegan <pg@bowt.ie>
Reviewed-By: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/CADkLM=ded21M9iZ36hHm-vj2rE2d=zcKpUQMds__Xm2pxLfHKA@mail.gmail.com

doc/src/sgml/monitoring.sgml
src/backend/access/heap/heapam.c
src/backend/catalog/system_views.sql
src/backend/utils/activity/pgstat_relation.c
src/backend/utils/adt/pgstatfuncs.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/include/pgstat.h
src/test/regress/expected/rules.out

index 7ab4424bf13dc9ff7ca02fde58117326904a4a72..21e6ce2841e3288bc9d7b529016c4451248c4038 100644 (file)
@@ -4789,7 +4789,7 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
        <structfield>n_tup_ins</structfield> <type>bigint</type>
       </para>
       <para>
-       Number of rows inserted
+       Total number of rows inserted
       </para></entry>
      </row>
 
@@ -4798,7 +4798,10 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
        <structfield>n_tup_upd</structfield> <type>bigint</type>
       </para>
       <para>
-       Number of rows updated (includes <link linkend="storage-hot">HOT updated rows</link>)
+       Total number of rows updated.  (This includes row updates
+       counted in <structfield>n_tup_hot_upd</structfield> and
+       <structfield>n_tup_newpage_upd</structfield>, and remaining
+       non-<acronym>HOT</acronym> updates.)
       </para></entry>
      </row>
 
@@ -4807,7 +4810,7 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
        <structfield>n_tup_del</structfield> <type>bigint</type>
       </para>
       <para>
-       Number of rows deleted
+       Total number of rows deleted
       </para></entry>
      </row>
 
@@ -4816,8 +4819,23 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
        <structfield>n_tup_hot_upd</structfield> <type>bigint</type>
       </para>
       <para>
-       Number of rows HOT updated (i.e., with no separate index
-       update required)
+       Number of rows <link linkend="storage-hot">HOT updated</link>.
+       These are updates where no successor versions are required in
+       indexes.
+      </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 successor version goes onto a
+       <emphasis>new</emphasis> heap page, leaving behind an original
+       version with a
+       <link linkend="storage-tuple-layout"><structfield>t_ctid</structfield>
+        field</link> that points to a different heap page.  These are
+       always non-<acronym>HOT</acronym> updates.
       </para></entry>
      </row>
 
index cf4b917eb4b21f9e939ad696fc44c4118980dc1c..8abc101c8cb01fd98a59e239c8f32b3e6d0aa383 100644 (file)
@@ -3803,7 +3803,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, use_hot_update, newbuf != buffer);
 
    /*
     * If heaptup is a private copy, release it.  Don't forget to copy t_self
index 34ca0e739f55348e9446bae37540acdfa4102077..8ea159dbded88324c6e9352193d54317fd7b0087 100644 (file)
@@ -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)
index f793ac1516546c6f625347850a4e642073ea6a67..b576433797a888412e940295fbd1054c2fbfe2fa 100644 (file)
@@ -373,8 +373,10 @@ 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, bool hot, bool newpage)
 {
+   Assert(!(hot && newpage));
+
    if (pgstat_should_count_relation(rel))
    {
        PgStat_TableStatus *pgstat_info = rel->pgstat_info;
@@ -382,9 +384,14 @@ pgstat_count_heap_update(Relation rel, bool hot)
        ensure_tabstat_xact_level(pgstat_info);
        pgstat_info->trans->tuples_updated++;
 
-       /* t_tuples_hot_updated is nontransactional, so just advance it */
+       /*
+        * t_tuples_hot_updated and t_tuples_newpage_updated counters are
+        * nontransactional, so just advance them
+        */
        if (hot)
            pgstat_info->t_counts.t_tuples_hot_updated++;
+       else if (newpage)
+           pgstat_info->t_counts.t_tuples_newpage_updated++;
    }
 }
 
@@ -804,6 +811,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.
index 35c6d465553f5293bed95bdb79fdfcfba3c43c78..56119737c892cc9684c6a52bb2d252faf13ab2da 100644 (file)
@@ -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)
 
@@ -1618,6 +1621,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)
 {
index e94528a7c71e8c8a3017d7f415e034e0747dc5cd..69270c313f6084f0afbe0d7de7f72b4ff51e3981 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202303181
+#define CATALOG_VERSION_NO 202303231
 
 #endif
index 5cf87aeb2c41c34533b3ef6f182e28cb008dd0af..7c358cff162ac40f87a5e10a37f1e89d5b0c6300 100644 (file)
   proname => 'pg_stat_get_tuples_hot_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_tuples_hot_updated' },
+{ oid => '8614',
+  descr => 'statistics: number of tuples updated onto a new page',
+  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',
   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 => '8615',
+  descr => 'statistics: number of tuples updated onto a new page 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',
index 1e418b682b56dab59caf57540e804de533f70357..6dd14004beb842f0f788165fcd6534b2d0cdad69 100644 (file)
@@ -151,8 +151,8 @@ 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,
- * regardless of whether the transaction committed.  delta_live_tuples,
+ * 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!
  * ----------
@@ -168,6 +168,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;
@@ -401,6 +402,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;
@@ -616,7 +618,7 @@ 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, bool hot, bool newpage);
 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);
index e953d1f5159493ee81fbec7cc1b0af75c85b4d01..996d22b7ddf8ec5a3dbcf06320a32dd697556368 100644 (file)
@@ -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,
@@ -2146,6 +2147,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,
@@ -2193,6 +2195,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,
@@ -2244,7 +2247,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)))
@@ -2260,7 +2264,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,
@@ -2282,7 +2287,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,