Re: Autovacuum on partitioned table (autoanalyze) - Mailing list pgsql-hackers

From Kyotaro Horiguchi
Subject Re: Autovacuum on partitioned table (autoanalyze)
Date
Msg-id 20201110.203557.1420746510378864931.horikyota.ntt@gmail.com
Whole thread Raw
In response to Re: Autovacuum on partitioned table (autoanalyze)  (yuzuko <yuzukohosoya@gmail.com>)
Responses Re: Autovacuum on partitioned table (autoanalyze)  (Alvaro Herrera <alvherre@alvh.no-ip.org>)
Re: Autovacuum on partitioned table (autoanalyze)  (Alvaro Herrera <alvherre@alvh.no-ip.org>)
List pgsql-hackers
At Thu, 5 Nov 2020 16:03:12 +0900, yuzuko <yuzukohosoya@gmail.com> wrote in 
> Hi Justin,
> 
> Thank you for your comments.
> I attached the latest patch(v11) to the previous email.
> 
> >
> > +                * Get its all ancestors to propagate changes_since_analyze count.
> > +                * However, when ANALYZE inheritance tree, we get ancestors of
> > +                * toprel_oid to avoid needless counting.
> >
> > => I don't understand that comment.
> >
> I fixed that comment.

+         * Get its all ancestors to propagate changes_since_analyze count.
+         * However, when we have a valid toprel_oid, that is ANALYZE inheritance
+         * tree, if we propagate the number to all ancestors, the next analyze
+         * on partitioned tables in the tree could happen shortly expected.
+         * So we get ancestors of toprel_oid which are not analyzed this time.

In second thought about the reason for the "toprel_oid". It is perhaps
to avoid "wrongly" propagated values to ancestors after a manual
ANALYZE on a partitioned table.  But the same happens after an
autoanalyze iteration if some of the ancestors of a leaf relation are
analyzed before the leaf relation in a autoanalyze iteration.  That
can trigger an unnecessary analyzing for some of the ancestors.
So we need to do a similar thing for autovacuum, However..

  [1(root):analzye]-[2:DONT analyze]-[3:analyze]-[leaf]

In this case topre_oid is invalid (since it's autoanalyze) but we
should avoid propagating the count to 1 and 3 if it is processed
*before* the leaf, but should propagate to 2. toprel_oid doesn't work
in that case.

So, to propagate the count properly, we need to analyze relations
leaf-to-root order, or propagate the counter only to anscestors that
haven't been processed in the current iteration.  It seems a bit too
complex to sort analyze relations in that order.  The latter would be
relatively simple.  See the attached for how it looks like.

Anyway, either way we take, it is not pgstat.c's responsibility to do
that since the former need to heavily reliant to what analyze does,
and the latter need to know what anlyze is doing.


> > Also, you called SearchSysCacheCopy1, but didn't free the tuple.  I don't think
> > you need to copy it anyway - just call ReleaseSysCache().
> >
> Fixed it.

Mmm. Unfortunately, that fix leaks cache reference when
!RELKIND_HAS_STORAGE.

> > Regarding the counters in pg_stat_all_tables: maybe some of these should be
> > null rather than zero ?  Or else you should make an 0001 patch to fully
> > implement this view, with all relevant counters, not just n_mod_since_analyze,
> > last_*analyze, and *analyze_count.  These are specifically misleading:
> >
> > last_vacuum         |
> > last_autovacuum     |
> > n_ins_since_vacuum  | 0
> > vacuum_count        | 0
> > autovacuum_count    | 0
> >
> I haven't modified this part yet, but you meant that we should set
> null to counters
> about vacuum because partitioned tables are not vacuumed?

Perhaps bacause partitioned tables *cannot* be vacuumed.  I'm not sure
what is the best way here.  Showing null seems reasonable but I'm not
sure that doesn't break anything.


regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center
diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 4d8ad754f8..50b55f5d01 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -816,6 +816,18 @@ analyze threshold = analyze base threshold + analyze scale factor * number of tu
     since the last <command>ANALYZE</command>.
    </para>
 
+   <para>
+    For declaratively partitioned tables, only analyze is supported.
+    The same <quote>analyze threshold</quote> defined above is used,
+    but the number of tuples is sum of their childrens'
+    <structname>pg_class</structname>.<structfield>reltuples</structfield>.
+    Also, total number of tuples inserted, updated, or deleted since the last
+    <command>ANALYZE</command> compared with the threshold is calculated by adding up
+    childrens' number of tuples analyzed in the previous <command>ANALYZE</command>.
+    This is because partitioned tables don't have any data.  So analyze on partitioned
+    tables are one lap behind their children.
+   </para>
+
    <para>
     Temporary tables cannot be accessed by autovacuum.  Therefore,
     appropriate vacuum and analyze operations should be performed via
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bc59a2d77d..d94caa4b7e 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1323,8 +1323,6 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     If a table parameter value is set and the
     equivalent <literal>toast.</literal> parameter is not, the TOAST table
     will use the table's parameter value.
-    Specifying these parameters for partitioned tables is not supported,
-    but you may specify them for individual leaf partitions.
    </para>
 
    <variablelist>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228a8c..35bc2e5bdb 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -108,7 +108,7 @@ static relopt_bool boolRelOpts[] =
         {
             "autovacuum_enabled",
             "Enables autovacuum in this relation",
-            RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+            RELOPT_KIND_HEAP | RELOPT_KIND_TOAST | RELOPT_KIND_PARTITIONED,
             ShareUpdateExclusiveLock
         },
         true
@@ -246,7 +246,7 @@ static relopt_int intRelOpts[] =
         {
             "autovacuum_analyze_threshold",
             "Minimum number of tuple inserts, updates or deletes prior to analyze",
-            RELOPT_KIND_HEAP,
+            RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
             ShareUpdateExclusiveLock
         },
         -1, 0, INT_MAX
@@ -420,7 +420,7 @@ static relopt_real realRelOpts[] =
         {
             "autovacuum_analyze_scale_factor",
             "Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples",
-            RELOPT_KIND_HEAP,
+            RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
             ShareUpdateExclusiveLock
         },
         -1, 0.0, 100.0
@@ -1961,13 +1961,12 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+
     /*
-     * There are no options for partitioned tables yet, but this is able to do
-     * some validation.
+     * autovacuum_enabled, autovacuum_analyze_threshold and
+     * autovacuum_analyze_scale_factor are supported for partitioned tables.
      */
-    return (bytea *) build_reloptions(reloptions, validate,
-                                      RELOPT_KIND_PARTITIONED,
-                                      0, NULL, 0);
+    return default_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED);
 }
 
 /*
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2e4aa1c4b6..f1982d0f77 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -591,7 +591,7 @@ CREATE VIEW pg_stat_all_tables AS
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't', 'm')
+    WHERE C.relkind IN ('r', 't', 'm', 'p')
     GROUP BY C.oid, N.nspname, C.relname;
 
 CREATE VIEW pg_stat_xact_all_tables AS
@@ -611,7 +611,7 @@ CREATE VIEW pg_stat_xact_all_tables AS
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't', 'm')
+    WHERE C.relkind IN ('r', 't', 'm', 'p')
     GROUP BY C.oid, N.nspname, C.relname;
 
 CREATE VIEW pg_stat_sys_tables AS
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8af12b5c6b..8758a24955 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -30,6 +30,7 @@
 #include "catalog/catalog.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
+#include "catalog/partition.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
@@ -38,6 +39,7 @@
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
+#include "common/hashfn.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -107,6 +109,45 @@ static void update_attstats(Oid relid, bool inh,
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
+typedef struct analyze_oident
+{
+    Oid oid;
+    char status;
+} analyze_oident;
+
+StaticAssertDecl(sizeof(Oid) == 4, "oid is not compatible with uint32");
+#define SH_PREFIX analyze_oids
+#define SH_ELEMENT_TYPE analyze_oident
+#define SH_KEY_TYPE Oid
+#define SH_KEY oid
+#define SH_HASH_KEY(tb, key) hash_bytes_uint32(key)
+#define SH_EQUAL(tb, a, b) (a == b)
+#define SH_SCOPE static inline
+#define SH_DEFINE
+#define SH_DECLARE
+#include "lib/simplehash.h"
+
+#define ANALYZED_OIDS_HASH_SIZE 128
+analyze_oids_hash *analyzed_reloids = NULL;
+
+void
+analyze_init_status(void)
+{
+    if (analyzed_reloids)
+        analyze_oids_destroy(analyzed_reloids);
+
+    analyzed_reloids = analyze_oids_create(CurrentMemoryContext,
+                                           ANALYZED_OIDS_HASH_SIZE, NULL);
+}
+
+void
+analyze_destroy_status(void)
+{
+    if (analyzed_reloids)
+        analyze_oids_destroy(analyzed_reloids);
+
+    analyzed_reloids = NULL;
+}
 
 /*
  *    analyze_rel() -- analyze one relation
@@ -312,6 +353,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
     Oid            save_userid;
     int            save_sec_context;
     int            save_nestlevel;
+    bool        found;
 
     if (inh)
         ereport(elevel,
@@ -644,16 +686,67 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
     }
 
     /*
-     * Report ANALYZE to the stats collector, too.  However, if doing
-     * inherited stats we shouldn't report, because the stats collector only
-     * tracks per-table stats.  Reset the changes_since_analyze counter only
-     * if we analyzed all columns; otherwise, there is still work for
-     * auto-analyze to do.
+     * Report ANALYZE to the stats collector, too.  Regarding inherited stats,
+     * we report only in the case of declarative partitioning.  Reset the
+     * changes_since_analyze counter only if we analyzed all columns;
+     * otherwise, there is still work for auto-analyze to do.
      */
-    if (!inh)
+    if (!inh || onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+    {
+        List     *ancestors;
+        ListCell *lc;
+        Datum    oiddatum = ObjectIdGetDatum(RelationGetRelid(onerel));
+        Datum    countdatum;
+        int64    change_count;
+
+        /*
+         * Read current value of n_mod_since_analyze of this relation.  This
+         * might be a bit stale but we don't need such correctness here.
+         */
+        countdatum =
+            DirectFunctionCall1(pg_stat_get_mod_since_analyze, oiddatum);
+        change_count = DatumGetInt64(countdatum);
+
+        /* collect all ancestors of this relation */
+        ancestors = get_partition_ancestors(RelationGetRelid(onerel));
+
+        /*
+         * To let partitioned relations be analyzed, we need to update
+         * change_since_analyze also for partitioned relations, which don't
+         * have storage.  We move the count of leaf-relations to ancestors
+         * before resetting.  We could instead bump up the counter of all
+         * ancestors every time leaf relations are updated but that is too
+         * complex.
+         */
+        foreach (lc, ancestors)
+        {
+            Oid toreloid = lfirst_oid(lc);
+
+            /*
+             * Don't propagate the count to anscestors that have already been
+             * analyzed in this analyze command or this iteration of
+             * autoanalyze.
+             */
+            if (analyze_oids_lookup(analyzed_reloids, toreloid) == NULL)
+            {
+                Relation rel;
+
+                rel = table_open(toreloid, AccessShareLock);
+                pgstat_report_partchanges(rel, change_count);
+                table_close(rel, AccessShareLock);
+            }
+
+        }
+
         pgstat_report_analyze(onerel, totalrows, totaldeadrows,
                               (va_cols == NIL));
 
+        list_free(ancestors);
+    }
+
+    /* Recrod this relatoin as "analyzed"  */
+    analyze_oids_insert(analyzed_reloids, onerel->rd_id, &found);
+
     /* If this isn't part of VACUUM ANALYZE, let index AMs do cleanup */
     if (!(params->options & VACOPT_VACUUM))
     {
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 1b6717f727..9cc7c1bb4f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -437,6 +437,7 @@ vacuum(List *relations, VacuumParams *params,
         VacuumSharedCostBalance = NULL;
         VacuumActiveNWorkers = NULL;
 
+        analyze_init_status();
         /*
          * Loop to process each selected relation.
          */
@@ -487,6 +488,7 @@ vacuum(List *relations, VacuumParams *params,
     {
         in_vacuum = false;
         VacuumCostActive = false;
+        analyze_destroy_status();
     }
     PG_END_TRY();
 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 2cef56f115..bb568f68b5 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -75,6 +75,7 @@
 #include "catalog/dependency.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_inherits.h"
 #include "commands/dbcommands.h"
 #include "commands/vacuum.h"
 #include "lib/ilist.h"
@@ -2052,11 +2053,11 @@ do_autovacuum(void)
      * Scan pg_class to determine which tables to vacuum.
      *
      * We do this in two passes: on the first one we collect the list of plain
-     * relations and materialized views, and on the second one we collect
-     * TOAST tables. The reason for doing the second pass is that during it we
-     * want to use the main relation's pg_class.reloptions entry if the TOAST
-     * table does not have any, and we cannot obtain it unless we know
-     * beforehand what's the main table OID.
+     * relations, materialized views and partitioned tables, and on the second
+     * one we collect TOAST tables. The reason for doing the second pass is that
+     * during it we want to use the main relation's pg_class.reloptions entry
+     * if the TOAST table does not have any, and we cannot obtain it unless we
+     * know beforehand what's the main table OID.
      *
      * We need to check TOAST tables separately because in cases with short,
      * wide tables there might be proportionally much more activity in the
@@ -2079,7 +2080,8 @@ do_autovacuum(void)
         bool        wraparound;
 
         if (classForm->relkind != RELKIND_RELATION &&
-            classForm->relkind != RELKIND_MATVIEW)
+            classForm->relkind != RELKIND_MATVIEW &&
+            classForm->relkind != RELKIND_PARTITIONED_TABLE)
             continue;
 
         relid = classForm->oid;
@@ -2742,6 +2744,7 @@ extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc)
 
     Assert(((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_RELATION ||
            ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW ||
+           ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_PARTITIONED_TABLE ||
            ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE);
 
     relopts = extractRelOptions(tup, pg_class_desc, NULL);
@@ -3091,7 +3094,42 @@ relation_needs_vacanalyze(Oid relid,
      */
     if (PointerIsValid(tabentry) && AutoVacuumingActive())
     {
-        reltuples = classForm->reltuples;
+        if (classForm->relkind != RELKIND_PARTITIONED_TABLE)
+            reltuples = classForm->reltuples;
+        else
+        {
+            /*
+             * If the relation is a partitioned table, we must add up childrens'
+             * reltuples.
+             */
+            List     *children;
+            ListCell *lc;
+
+            reltuples = 0;
+
+            /* Find all members of inheritance set taking AccessShareLock */
+            children = find_all_inheritors(relid, AccessShareLock, NULL);
+
+            foreach(lc, children)
+            {
+                Oid        childOID = lfirst_oid(lc);
+                HeapTuple  childtuple;
+                Form_pg_class childclass;
+
+                childtuple = SearchSysCache1(RELOID, ObjectIdGetDatum(childOID));
+                childclass = (Form_pg_class) GETSTRUCT(childtuple);
+
+                /* Skip a partitioned table and foreign partitions */
+                if (RELKIND_HAS_STORAGE(childclass->relkind))
+                {
+                    /* Sum up the child's reltuples for its parent table */
+                    reltuples += childclass->reltuples;
+                }
+
+                ReleaseSysCache(childtuple);
+            }
+        }
+
         vactuples = tabentry->n_dead_tuples;
         instuples = tabentry->inserts_since_vacuum;
         anltuples = tabentry->changes_since_analyze;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index e76e627c6b..633c5743fb 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -360,6 +360,7 @@ static void pgstat_recv_resetreplslotcounter(PgStat_MsgResetreplslotcounter *msg
 static void pgstat_recv_autovac(PgStat_MsgAutovacStart *msg, int len);
 static void pgstat_recv_vacuum(PgStat_MsgVacuum *msg, int len);
 static void pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len);
+static void pgstat_recv_partchanges(PgStat_MsgPartChanges *msg, int len);
 static void pgstat_recv_archiver(PgStat_MsgArchiver *msg, int len);
 static void pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len);
 static void pgstat_recv_wal(PgStat_MsgWal *msg, int len);
@@ -1556,6 +1557,9 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
  *
  * Caller must provide new live- and dead-tuples estimates, as well as a
  * flag indicating whether to reset the changes_since_analyze counter.
+ * Exceptional support only changes_since_analyze for partitioned tables,
+ * though they don't have any data.  This counter will tell us whether
+ * partitioned tables need autoanalyze or not.
  * --------
  */
 void
@@ -1576,22 +1580,29 @@ pgstat_report_analyze(Relation rel,
      * off these counts from what we send to the collector now, else they'll
      * be double-counted after commit.  (This approach also ensures that the
      * collector ends up with the right numbers if we abort instead of
-     * committing.)
+     * committing.)  However, for partitioned tables, we will not report both
+     * livetuples and deadtuples because those tables don't have any data.
      */
     if (rel->pgstat_info != NULL)
     {
         PgStat_TableXactStatus *trans;
 
-        for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
+        if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+            /* If this rel is partitioned, skip modifying */
+            livetuples = deadtuples = 0;
+        else
         {
-            livetuples -= trans->tuples_inserted - trans->tuples_deleted;
-            deadtuples -= trans->tuples_updated + trans->tuples_deleted;
+            for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
+            {
+                livetuples -= trans->tuples_inserted - trans->tuples_deleted;
+                deadtuples -= trans->tuples_updated + trans->tuples_deleted;
+            }
+            /* count stuff inserted by already-aborted subxacts, too */
+            deadtuples -= rel->pgstat_info->t_counts.t_delta_dead_tuples;
+            /* Since ANALYZE's counts are estimates, we could have underflowed */
+            livetuples = Max(livetuples, 0);
+            deadtuples = Max(deadtuples, 0);
         }
-        /* count stuff inserted by already-aborted subxacts, too */
-        deadtuples -= rel->pgstat_info->t_counts.t_delta_dead_tuples;
-        /* Since ANALYZE's counts are estimates, we could have underflowed */
-        livetuples = Max(livetuples, 0);
-        deadtuples = Max(deadtuples, 0);
     }
 
     pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_ANALYZE);
@@ -1605,6 +1616,30 @@ pgstat_report_analyze(Relation rel,
     pgstat_send(&msg, sizeof(msg));
 }
 
+/* --------
+ * pgstat_report_partchanges() -
+ *
+ *
+ *  Called when a leaf partition is analyzed to tell the collector about
+ *  its parent's changed_tuples.
+ * --------
+ */
+void
+pgstat_report_partchanges(Relation rel, PgStat_Counter changed_tuples)
+{
+    PgStat_MsgPartChanges  msg;
+
+    if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_counts)
+        return;
+
+    pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_PARTCHANGES);
+    msg.m_databaseid = rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId;
+    msg.m_tableoid = RelationGetRelid(rel);
+    msg.m_changed_tuples = changed_tuples;
+    pgstat_send(&msg, sizeof(msg));
+}
+
+
 /* --------
  * pgstat_report_recovery_conflict() -
  *
@@ -1921,7 +1956,8 @@ pgstat_initstats(Relation rel)
     char        relkind = rel->rd_rel->relkind;
 
     /* We only count stats for things that have storage */
-    if (!RELKIND_HAS_STORAGE(relkind))
+    if (!RELKIND_HAS_STORAGE(relkind) &&
+        relkind != RELKIND_PARTITIONED_TABLE)
     {
         rel->pgstat_info = NULL;
         return;
@@ -4833,6 +4869,10 @@ PgstatCollectorMain(int argc, char *argv[])
                     pgstat_recv_analyze(&msg.msg_analyze, len);
                     break;
 
+                case PGSTAT_MTYPE_PARTCHANGES:
+                    pgstat_recv_partchanges(&msg.msg_partchanges, len);
+                    break;
+
                 case PGSTAT_MTYPE_ARCHIVER:
                     pgstat_recv_archiver(&msg.msg_archiver, len);
                     break;
@@ -6701,6 +6741,18 @@ pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len)
     }
 }
 
+static void
+pgstat_recv_partchanges(PgStat_MsgPartChanges *msg, int len)
+{
+    PgStat_StatDBEntry   *dbentry;
+    PgStat_StatTabEntry  *tabentry;
+
+    dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
+
+    tabentry = pgstat_get_tab_entry(dbentry, msg->m_tableoid, true);
+
+    tabentry->changes_since_analyze += msg->m_changed_tuples;
+}
 
 /* ----------
  * pgstat_recv_archiver() -
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..c6dcf23898 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -280,6 +280,8 @@ extern Relation vacuum_open_relation(Oid relid, RangeVar *relation,
                                      int options, bool verbose, LOCKMODE lmode);
 
 /* in commands/analyze.c */
+extern void analyze_init_status(void);
+extern void analyze_destroy_status(void);
 extern void analyze_rel(Oid relid, RangeVar *relation,
                         VacuumParams *params, List *va_cols, bool in_outer_xact,
                         BufferAccessStrategy bstrategy);
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 257e515bfe..dd7faf9861 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -60,6 +60,7 @@ typedef enum StatMsgType
     PGSTAT_MTYPE_AUTOVAC_START,
     PGSTAT_MTYPE_VACUUM,
     PGSTAT_MTYPE_ANALYZE,
+    PGSTAT_MTYPE_PARTCHANGES,
     PGSTAT_MTYPE_ARCHIVER,
     PGSTAT_MTYPE_BGWRITER,
     PGSTAT_MTYPE_WAL,
@@ -419,6 +420,18 @@ typedef struct PgStat_MsgAnalyze
     PgStat_Counter m_dead_tuples;
 } PgStat_MsgAnalyze;
 
+/* ----------
+ * PgStat_MsgPartChanges            Sent by the autovacuum deamon
+ *                                  after ANALYZE of leaf partitions
+ * ----------
+ */
+typedef struct PgStat_MsgPartChanges
+{
+    PgStat_MsgHdr m_hdr;
+    Oid           m_databaseid;
+    Oid           m_tableoid;
+    PgStat_Counter m_changed_tuples;
+} PgStat_MsgPartChanges;
 
 /* ----------
  * PgStat_MsgArchiver            Sent by the archiver to update statistics.
@@ -640,6 +653,7 @@ typedef union PgStat_Msg
     PgStat_MsgAutovacStart msg_autovacuum_start;
     PgStat_MsgVacuum msg_vacuum;
     PgStat_MsgAnalyze msg_analyze;
+    PgStat_MsgPartChanges msg_partchanges;
     PgStat_MsgArchiver msg_archiver;
     PgStat_MsgBgWriter msg_bgwriter;
     PgStat_MsgWal msg_wal;
@@ -1387,7 +1401,7 @@ extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 extern void pgstat_report_analyze(Relation rel,
                                   PgStat_Counter livetuples, PgStat_Counter deadtuples,
                                   bool resetcounter);
-
+extern void pgstat_report_partchanges(Relation rel, PgStat_Counter changed_tuples);
 extern void pgstat_report_recovery_conflict(int reason);
 extern void pgstat_report_deadlock(void);
 extern void pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 097ff5d111..e19e510245 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1804,7 +1804,7 @@ pg_stat_all_tables| SELECT c.oid AS relid,
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
-  WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]))
+  WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
 pg_stat_archiver| SELECT s.archived_count,
     s.last_archived_wal,
@@ -2172,7 +2172,7 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
-  WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]))
+  WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
 pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
     pg_stat_xact_all_tables.schemaname,

pgsql-hackers by date:

Previous
From: "Hou, Zhijie"
Date:
Subject: Add Nullif case for eval_const_expressions_mutator
Next
From: Kyotaro Horiguchi
Date:
Subject: Re: Allow some recovery parameters to be changed with reload