diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 4a2b6f0dae..88c635f82f 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1310,8 +1310,6 @@ WITH ( MODULUS numeric_literal, REM If a table parameter value is set and the equivalent toast. 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. diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 79430d2b7b..20183a96a4 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 @@ -227,7 +227,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 @@ -379,7 +379,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 @@ -1586,13 +1586,12 @@ build_reloptions(Datum reloptions, 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 f681aafcf9..161abb6450 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -584,7 +584,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 @@ -604,7 +604,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 c4420ddd7f..bbfdf75dbe 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -655,15 +655,15 @@ 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. If the table is a + * partition, report changes_since_analyze of its parent because + * autovacuum process for partitioned tables needs it. Reset the + * changes_since_analyze counter only if we analyzed all columns; + * otherwise, there is still work for auto-analyze to do. */ - if (!inh) - pgstat_report_analyze(onerel, totalrows, totaldeadrows, - (va_cols == NIL)); + 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 */ if (!(params->options & VACOPT_VACUUM)) diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 6d1f28c327..aabb1903de 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" @@ -2031,11 +2032,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 @@ -2058,7 +2059,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; @@ -2720,6 +2722,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); @@ -2990,6 +2993,42 @@ relation_needs_vacanalyze(Oid relid, AssertArg(OidIsValid(relid)); /* + * If the relation is a partitioned table, we must add up children's + * reltuples. + */ + if (classForm->relkind == RELKIND_PARTITIONED_TABLE) + { + 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; + + /* Ignore the parent table */ + if (childOID == relid) + continue; + + childtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(childOID)); + childclass = (Form_pg_class) GETSTRUCT(childtuple); + + /* Skip foreign partitions */ + if (childclass->relkind == RELKIND_FOREIGN_TABLE) + continue; + + /* Sum up the child's reltuples for its parent table */ + reltuples += childclass->reltuples; + } + } + + /* * Determine vacuum/analyze equation parameters. We have two possible * sources: the passed reloptions (which could be a main table or a toast * table), or the autovacuum GUC variables. @@ -3056,7 +3095,8 @@ relation_needs_vacanalyze(Oid relid, */ if (PointerIsValid(tabentry) && AutoVacuumingActive()) { - reltuples = classForm->reltuples; + if (classForm->relkind != RELKIND_PARTITIONED_TABLE) + reltuples = classForm->reltuples; vactuples = tabentry->n_dead_tuples; anltuples = tabentry->changes_since_analyze; diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 59dc4f31ab..ced2599050 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -38,6 +38,7 @@ #include "access/transam.h" #include "access/twophase_rmgr.h" #include "access/xact.h" +#include "catalog/partition.h" #include "catalog/pg_database.h" #include "catalog/pg_proc.h" #include "common/ip.h" @@ -67,6 +68,7 @@ #include "utils/ps_status.h" #include "utils/rel.h" #include "utils/snapmgr.h" +#include "utils/syscache.h" #include "utils/timestamp.h" /* ---------- @@ -322,6 +324,7 @@ static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter *msg, in 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_partanalyze(PgStat_MsgPartAnalyze *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_funcstat(PgStat_MsgFuncstat *msg, int len); @@ -1463,6 +1466,33 @@ pgstat_report_analyze(Relation rel, deadtuples = Max(deadtuples, 0); } + /* + * If the table is a leaf partition, tell the stats collector its parent's + * changes_since_analyze for auto analyze + */ + if (IsAutoVacuumWorkerProcess() && + rel->rd_rel->relispartition && + !(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)) + { + Oid parentoid; + Relation parentrel; + PgStat_StatDBEntry *dbentry; + PgStat_StatTabEntry *tabentry; + + /* Get its parent table's Oid and relation */ + parentoid = get_partition_parent(RelationGetRelid(rel)); + parentrel = table_open(parentoid, AccessShareLock); + + /* Fetch the pgstat for this table */ + dbentry = pgstat_fetch_stat_dbentry(MyDatabaseId); + tabentry = pgstat_get_tab_entry(dbentry, RelationGetRelid(rel), true); + + /* Report changes_since_analyze to the stats collector */ + pgstat_report_partanalyze(parentrel, tabentry->changes_since_analyze); + + table_close(parentrel, AccessShareLock); + } + pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_ANALYZE); msg.m_databaseid = rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId; msg.m_tableoid = RelationGetRelid(rel); @@ -1475,6 +1505,49 @@ pgstat_report_analyze(Relation rel, } /* -------- + * pgstat_report_partanalyze() - + * + * Tell the collector about the parent table of which partition just analyzed. + * + * Caller must provide a child's changes_since_analyze as a parents. + * -------- + */ +void +pgstat_report_partanalyze(Relation rel, PgStat_Counter changed_tuples) +{ + PgStat_MsgPartAnalyze msg; + + if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_counts) + return; + + /* + * If the partitioned table is also a partition, tell the stats collector + * its parent's changes_since_analyze for auto analyze + */ + if (rel->rd_rel->relispartition) + { + Oid parentoid; + Relation parentrel; + + /* Get its parent table's Oid and relation */ + parentoid = get_partition_parent(RelationGetRelid(rel)); + parentrel = table_open(parentoid, AccessShareLock); + + /* Report changes_since_analyze to the stats collector */ + pgstat_report_partanalyze(parentrel, changed_tuples); + + table_close(parentrel, AccessShareLock); + } + + pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_PARTANALYZE); + 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() - * * Tell the collector about a Hot Standby recovery conflict. @@ -1749,6 +1822,7 @@ pgstat_initstats(Relation rel) /* We only count stats for things that have storage */ if (!(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW || + relkind == RELKIND_PARTITIONED_TABLE || relkind == RELKIND_INDEX || relkind == RELKIND_TOASTVALUE || relkind == RELKIND_SEQUENCE)) @@ -4592,6 +4666,10 @@ PgstatCollectorMain(int argc, char *argv[]) pgstat_recv_analyze(&msg.msg_analyze, len); break; + case PGSTAT_MTYPE_PARTANALYZE: + pgstat_recv_partanalyze(&msg.msg_partanalyze, len); + break; + case PGSTAT_MTYPE_ARCHIVER: pgstat_recv_archiver(&msg.msg_archiver, len); break; @@ -6239,6 +6317,18 @@ pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len) } } +static void +pgstat_recv_partanalyze(PgStat_MsgPartAnalyze *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/pgstat.h b/src/include/pgstat.h index 3a65a51696..9bb872b171 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -57,6 +57,7 @@ typedef enum StatMsgType PGSTAT_MTYPE_AUTOVAC_START, PGSTAT_MTYPE_VACUUM, PGSTAT_MTYPE_ANALYZE, + PGSTAT_MTYPE_PARTANALYZE, PGSTAT_MTYPE_ARCHIVER, PGSTAT_MTYPE_BGWRITER, PGSTAT_MTYPE_FUNCSTAT, @@ -389,6 +390,18 @@ typedef struct PgStat_MsgAnalyze PgStat_Counter m_dead_tuples; } PgStat_MsgAnalyze; +/* ---------- + * PgStat_MsgPartAnalyze Sent by the backend or autovacuum daemon + * after ANALYZE for partitioned tables + * ---------- + */ +typedef struct PgStat_MsgPartAnalyze +{ + PgStat_MsgHdr m_hdr; + Oid m_databaseid; + Oid m_tableoid; + PgStat_Counter m_changed_tuples; +} PgStat_MsgPartAnalyze; /* ---------- * PgStat_MsgArchiver Sent by the archiver to update statistics. @@ -562,6 +575,7 @@ typedef union PgStat_Msg PgStat_MsgAutovacStart msg_autovacuum_start; PgStat_MsgVacuum msg_vacuum; PgStat_MsgAnalyze msg_analyze; + PgStat_MsgPartAnalyze msg_partanalyze; PgStat_MsgArchiver msg_archiver; PgStat_MsgBgWriter msg_bgwriter; PgStat_MsgFuncstat msg_funcstat; @@ -1267,7 +1281,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_partanalyze(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 634f8256f7..7e9f6de9cb 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1789,7 +1789,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, @@ -2117,7 +2117,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,