Thanks for the quick rework. I like this design much better and I think this is pretty close to committable. Here's a rebased copy with some small cleanups (most notably, avoid calling pgstat_propagate_changes when the partition doesn't have a tabstat entry; also, free the lists that are allocated in a couple of places).
I didn't actually verify that it works. -- Álvaro Herrera Valdivia, Chile "La primera ley de las demostraciones en vivo es: no trate de usar el sistema. Escriba un guión que no toque nada para no causar daños." (Jakob Nielsen)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index d897bbec2b..5554275e64 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 @@ -1962,12 +1962,11 @@ 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 5f2541d316..fb41b06539 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -660,7 +660,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 @@ -680,7 +680,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 f84616d3d2..35e9a2fc17 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -655,20 +655,22 @@ do_analyze_rel(Relation onerel, VacuumParams *params, InvalidMultiXactId, in_outer_xact); } + } - /* - * Now report ANALYZE to the stats collector. - * - * We deliberately don't report to the stats collector when doing - * inherited stats, 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. - */ + /* + * Now report ANALYZE to the stats collector. + * + * Regarding inherited stats, we report only in the case of declarative + * partitioning. For partitioning based on inheritance, 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. + */ + if (!inh || onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) pgstat_report_analyze(onerel, totalrows, totaldeadrows, (va_cols == NIL)); - } + /* * If this isn't part of VACUUM ANALYZE, let index AMs do cleanup. diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 23ef23c13e..7ca074a800 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -74,7 +74,9 @@ #include "access/xact.h" #include "catalog/dependency.h" #include "catalog/namespace.h" +#include "catalog/partition.h" #include "catalog/pg_database.h" +#include "catalog/pg_inherits.h" #include "commands/dbcommands.h" #include "commands/vacuum.h" #include "lib/ilist.h" @@ -350,6 +352,8 @@ static void autovac_report_activity(autovac_table *tab); static void autovac_report_workitem(AutoVacuumWorkItem *workitem, const char *nspname, const char *relname); static void avl_sigusr2_handler(SIGNAL_ARGS); +static void pgstat_propagate_changes(Form_pg_class classForm, + PgStat_StatTabEntry *tabentry); static void autovac_refresh_stats(void); @@ -2055,11 +2059,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 @@ -2082,7 +2086,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; @@ -2117,6 +2122,16 @@ do_autovacuum(void) tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared, shared, dbentry); + /* + * If this relation is a leaf partition, propagate + * changes_since_analyze counts to all ancestors. + */ + if (classForm->relispartition && tabentry && + !(classForm->relkind == RELKIND_PARTITIONED_TABLE)) + { + pgstat_propagate_changes(classForm, tabentry); + } + /* Check if it needs vacuum or analyze */ relation_needs_vacanalyze(relid, relopts, classForm, tabentry, effective_multixact_freeze_max_age, @@ -2745,6 +2760,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); @@ -3161,7 +3177,43 @@ 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 + * children's 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); + } + + list_free(children); + } + vactuples = tabentry->n_dead_tuples; instuples = tabentry->inserts_since_vacuum; anltuples = tabentry->changes_since_analyze; @@ -3312,6 +3364,61 @@ autovac_report_workitem(AutoVacuumWorkItem *workitem, pgstat_report_activity(STATE_RUNNING, activity); } +/* + * pgstat_propagate_changes + * + * Propagate changes_since_analyze counter to all of ancestors + * to analyze partitioned tables automatically + * + * We can decide whether a partitioned table needs auto analyze according to + * changes_since_analyze which is propagated from all of the leaf partitions. + * To know the correct difference of partitioned table from the last analyze, + * we should track changes_since_analyze_reported counter for leaf partitions + * as well as changes_since_analyze counter. While changes_since_analyze + * counter tracks the number of changed tuples from the last analyze per + * partitions, changes_since_analyze_reported counter tracks changes_since_analyze + * we already propagated to ancestors. Then, we propagate only the difference + * between these counters to the partitioned table. + */ +static void +pgstat_propagate_changes(Form_pg_class classForm, PgStat_StatTabEntry *tabentry) +{ + + float4 anltuples, + anltuples_reported, + change_count; + List *ancestors; + ListCell *lc; + Relation parentrel, + childrel; + + ancestors = get_partition_ancestors(classForm->oid); + + anltuples = tabentry->changes_since_analyze; + anltuples_reported = tabentry->changes_since_analyze_reported; + change_count = anltuples - anltuples_reported; + + /* update changes_since_analyze of ancestors */ + if (anltuples > 0 && change_count > 0) + { + foreach(lc, ancestors) + { + Oid relid = lfirst_oid(lc); + + parentrel = table_open(relid, AccessShareLock); + pgstat_report_partchanges(parentrel, change_count); + table_close(parentrel, AccessShareLock); + } + + /* update own changes_since_analyze_reported */ + childrel = table_open(classForm->oid, AccessShareLock); + pgstat_report_reportedchanges(childrel, change_count); + table_close(childrel, AccessShareLock); + } + + list_free(ancestors); +} + /* * AutoVacuumingActive * Check GUC vars and report whether the autovacuum process should be diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 4c4b072068..27316f598a 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -343,6 +343,8 @@ 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_reportedchanges(PgStat_MsgReportedChanges *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); @@ -1592,6 +1594,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 @@ -1613,23 +1618,31 @@ pgstat_report_analyze(Relation rel, * be double-counted after commit. (This approach also ensures that the * collector ends up with the right numbers if we abort instead of * committing.) + * + * For partitioned tables, we don't report live and dead tuples, because + * such 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); msg.m_databaseid = rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId; msg.m_tableoid = RelationGetRelid(rel); @@ -1641,6 +1654,49 @@ pgstat_report_analyze(Relation rel, pgstat_send(&msg, sizeof(msg)); } +/* -------- + * pgstat_report_partchanges() - + * + * Propagate changes_since_analyze counter from a leaf partition to its parent. + * -------- + */ +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_reportedchanges() - + * + * Tell the collector changes_since_analyze counter we have already + * propagated to its ancestors. + * -------- + */ +void +pgstat_report_reportedchanges(Relation rel, PgStat_Counter changed_tuples_reported) +{ + PgStat_MsgReportedChanges msg; + + if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_counts) + return; + + pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_REPORTEDCHANGES); + msg.m_databaseid = rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId; + msg.m_tableoid = RelationGetRelid(rel); + msg.m_changed_tuples_reported = changed_tuples_reported; + pgstat_send(&msg, sizeof(msg)); +} + /* -------- * pgstat_report_recovery_conflict() - * @@ -1958,7 +2014,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; @@ -3287,6 +3344,14 @@ 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_REPORTEDCHANGES: + pgstat_recv_reportedchanges(&msg.msg_reportedchanges, len); + break; + case PGSTAT_MTYPE_ARCHIVER: pgstat_recv_archiver(&msg.msg_archiver, len); break; @@ -3501,6 +3566,7 @@ pgstat_get_tab_entry(PgStat_StatDBEntry *dbentry, Oid tableoid, bool create) result->n_live_tuples = 0; result->n_dead_tuples = 0; result->changes_since_analyze = 0; + result->changes_since_analyze_reported = 0; result->inserts_since_vacuum = 0; result->blocks_fetched = 0; result->blocks_hit = 0; @@ -4768,6 +4834,7 @@ pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len) tabentry->n_live_tuples = tabmsg->t_counts.t_delta_live_tuples; tabentry->n_dead_tuples = tabmsg->t_counts.t_delta_dead_tuples; tabentry->changes_since_analyze = tabmsg->t_counts.t_changed_tuples; + tabentry->changes_since_analyze_reported = 0; tabentry->inserts_since_vacuum = tabmsg->t_counts.t_tuples_inserted; tabentry->blocks_fetched = tabmsg->t_counts.t_blocks_fetched; tabentry->blocks_hit = tabmsg->t_counts.t_blocks_hit; @@ -4803,6 +4870,7 @@ pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len) tabentry->n_live_tuples += tabmsg->t_counts.t_delta_live_tuples; tabentry->n_dead_tuples += tabmsg->t_counts.t_delta_dead_tuples; tabentry->changes_since_analyze += tabmsg->t_counts.t_changed_tuples; + tabentry->changes_since_analyze_reported = 0; tabentry->inserts_since_vacuum += tabmsg->t_counts.t_tuples_inserted; tabentry->blocks_fetched += tabmsg->t_counts.t_blocks_fetched; tabentry->blocks_hit += tabmsg->t_counts.t_blocks_hit; @@ -5159,7 +5227,10 @@ pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len) * have no good way to estimate how many of those there were. */ if (msg->m_resetcounter) + { tabentry->changes_since_analyze = 0; + tabentry->changes_since_analyze_reported = 0; + } if (msg->m_autovacuum) { @@ -5173,6 +5244,34 @@ 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; +} + + +static void +pgstat_recv_reportedchanges(PgStat_MsgReportedChanges *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_reported += msg->m_changed_tuples_reported; +} + + /* ---------- * pgstat_recv_archiver() - diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 7cd137506e..194003d5d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -69,6 +69,8 @@ typedef enum StatMsgType PGSTAT_MTYPE_AUTOVAC_START, PGSTAT_MTYPE_VACUUM, PGSTAT_MTYPE_ANALYZE, + PGSTAT_MTYPE_PARTCHANGES, + PGSTAT_MTYPE_REPORTEDCHANGES, PGSTAT_MTYPE_ARCHIVER, PGSTAT_MTYPE_BGWRITER, PGSTAT_MTYPE_WAL, @@ -126,6 +128,7 @@ typedef struct PgStat_TableCounts PgStat_Counter t_delta_live_tuples; PgStat_Counter t_delta_dead_tuples; PgStat_Counter t_changed_tuples; + PgStat_Counter t_changed_tuples_reported; PgStat_Counter t_blocks_fetched; PgStat_Counter t_blocks_hit; @@ -429,6 +432,32 @@ typedef struct PgStat_MsgAnalyze PgStat_Counter m_dead_tuples; } PgStat_MsgAnalyze; +/* ---------- + * PgStat_MsgPartChanges Sent by the autovacuum deamon to propagate + * the changed_tuples counter. + * ---------- + */ +typedef struct PgStat_MsgPartChanges +{ + PgStat_MsgHdr m_hdr; + Oid m_databaseid; + Oid m_tableoid; + PgStat_Counter m_changed_tuples; +} PgStat_MsgPartChanges; + +/* ---------- + * PgStat_MsgReportedChanges Sent by the autovacuum deamon to update + * changed_tuples_reported. + * ---------- + */ +typedef struct PgStat_MsgReportedChanges +{ + PgStat_MsgHdr m_hdr; + Oid m_databaseid; + Oid m_tableoid; + PgStat_Counter m_changed_tuples_reported; +} PgStat_MsgReportedChanges; + /* ---------- * PgStat_MsgArchiver Sent by the archiver to update statistics. @@ -674,6 +703,8 @@ typedef union PgStat_Msg PgStat_MsgAutovacStart msg_autovacuum_start; PgStat_MsgVacuum msg_vacuum; PgStat_MsgAnalyze msg_analyze; + PgStat_MsgPartChanges msg_partchanges; + PgStat_MsgReportedChanges msg_reportedchanges; PgStat_MsgArchiver msg_archiver; PgStat_MsgBgWriter msg_bgwriter; PgStat_MsgWal msg_wal; @@ -769,6 +800,7 @@ typedef struct PgStat_StatTabEntry PgStat_Counter n_live_tuples; PgStat_Counter n_dead_tuples; PgStat_Counter changes_since_analyze; + PgStat_Counter changes_since_analyze_reported; PgStat_Counter inserts_since_vacuum; PgStat_Counter blocks_fetched; @@ -975,7 +1007,8 @@ 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 changes_tuples); +extern void pgstat_report_reportedchanges(Relation rel, PgStat_Counter changes_tuples_reported); 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 9b59a7b4a5..954afb9a45 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1806,7 +1806,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, @@ -2209,7 +2209,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,