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..b6cbdf3471 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -655,13 +655,14 @@ 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. Reset the + * changes_since_analyze counter only if we analyzed all columns; + * otherwise, there is still work for auto-analyze to do. Also, + * if the table is a leaf partition, we add its current + * changes_since_analyze into its ancestors' counts because + * autovacuum process for partitioned tables needs it. */ - if (!inh) + if (!inh || onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) pgstat_report_analyze(onerel, totalrows, totaldeadrows, (va_cols == NIL)); diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index d625d17bf4..546c35652d 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -828,9 +828,9 @@ expand_vacuum_rel(VacuumRelation *vrel, int options) * later. */ oldcontext = MemoryContextSwitchTo(vac_context); - vacrels = lappend(vacrels, makeVacuumRelation(NULL, - part_oid, - vrel->va_cols)); + vacrels = lcons(makeVacuumRelation(NULL, part_oid, vrel->va_cols), + vacrels); + MemoryContextSwitchTo(oldcontext); } } diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 6d1f28c327..eed391f3cd 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,38 @@ 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; + + 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 +3091,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..d0c9e14403 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_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_funcstat(PgStat_MsgFuncstat *msg, int len); @@ -1463,18 +1466,76 @@ pgstat_report_analyze(Relation rel, deadtuples = Max(deadtuples, 0); } + /* + * For a leaf partition, add its current changes_since_analyze + * into its ancestors' counts. This must be done before sending + * the ANALYZE message as it resets the partition's changes_since_analze + * counter. + */ + if (rel->rd_rel->relispartition && + !(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)) + + { + List *ancestors; + ListCell *lc; + PgStat_StatDBEntry *dbentry; + PgStat_StatTabEntry *tabentry; + + /* Fetch the pgstat for this table */ + dbentry = pgstat_fetch_stat_dbentry(MyDatabaseId); + tabentry = pgstat_get_tab_entry(dbentry, RelationGetRelid(rel), true); + + /* Get its all ancestors */ + ancestors = get_partition_ancestors(RelationGetRelid(rel)); + foreach(lc, ancestors) + { + Oid parentOid = lfirst_oid(lc); + Relation parentrel = table_open(parentOid, AccessShareLock); + + /* Report changes_since_analyze to the stats collector */ + pgstat_report_partchanges(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); msg.m_autovacuum = IsAutoVacuumWorkerProcess(); msg.m_resetcounter = resetcounter; msg.m_analyzetime = GetCurrentTimestamp(); - msg.m_live_tuples = livetuples; - msg.m_dead_tuples = deadtuples; + msg.m_live_tuples = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ? 0 /* partitioned tables don't have any data, so it's 0 */ + : livetuples; + msg.m_dead_tuples = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ? 0 /* partitioned tables don't have any data, so it's 0 */ + : deadtuples; pgstat_send(&msg, sizeof(msg)); } /* -------- + * pgstat_report_partchanges() - + * + * Tell the collector about the parent table of which partition just analyzed. + * -------- + */ +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() - * * Tell the collector about a Hot Standby recovery conflict. @@ -1749,6 +1810,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 +4654,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; @@ -6239,6 +6305,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/pgstat.h b/src/include/pgstat.h index 3a65a51696..b979ed32d4 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_PARTCHANGES, 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_MsgPartChanges Sent by the autovacuum daemon + * 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. @@ -562,6 +575,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_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_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 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, diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out index 0cfe28e63f..abf5be31ca 100644 --- a/src/test/regress/expected/vacuum.out +++ b/src/test/regress/expected/vacuum.out @@ -192,9 +192,9 @@ VACUUM (FULL) vacparted; VACUUM (FREEZE) vacparted; -- check behavior with duplicate column mentions VACUUM ANALYZE vacparted(a,b,a); -ERROR: column "a" of relation "vacparted" appears more than once +ERROR: column "a" of relation "vacparted1" appears more than once ANALYZE vacparted(a,b,b); -ERROR: column "b" of relation "vacparted" appears more than once +ERROR: column "b" of relation "vacparted1" appears more than once -- multiple tables specified VACUUM vaccluster, vactst; VACUUM vacparted, does_not_exist; @@ -213,7 +213,7 @@ ANALYZE vacparted (b), vactst; ANALYZE vactst, does_not_exist, vacparted; ERROR: relation "does_not_exist" does not exist ANALYZE vactst (i), vacparted (does_not_exist); -ERROR: column "does_not_exist" of relation "vacparted" does not exist +ERROR: column "does_not_exist" of relation "vacparted1" does not exist ANALYZE vactst, vactst; BEGIN; -- ANALYZE behaves differently inside a transaction block ANALYZE vactst, vactst; @@ -287,24 +287,24 @@ WARNING: skipping "pg_authid" --- only superuser can vacuum it -- independently. VACUUM vacowned_parted; WARNING: skipping "vacowned_parted" --- only table or database owner can vacuum it -WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it +WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it VACUUM vacowned_part1; WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it VACUUM vacowned_part2; WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it ANALYZE vacowned_parted; WARNING: skipping "vacowned_parted" --- only table or database owner can analyze it -WARNING: skipping "vacowned_part1" --- only table or database owner can analyze it WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it +WARNING: skipping "vacowned_part1" --- only table or database owner can analyze it ANALYZE vacowned_part1; WARNING: skipping "vacowned_part1" --- only table or database owner can analyze it ANALYZE vacowned_part2; WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it VACUUM (ANALYZE) vacowned_parted; WARNING: skipping "vacowned_parted" --- only table or database owner can vacuum it -WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it +WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it VACUUM (ANALYZE) vacowned_part1; WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it VACUUM (ANALYZE) vacowned_part2; @@ -357,22 +357,22 @@ ALTER TABLE vacowned_parted OWNER TO regress_vacuum; ALTER TABLE vacowned_part1 OWNER TO CURRENT_USER; SET ROLE regress_vacuum; VACUUM vacowned_parted; -WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it +WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it VACUUM vacowned_part1; WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it VACUUM vacowned_part2; WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it ANALYZE vacowned_parted; -WARNING: skipping "vacowned_part1" --- only table or database owner can analyze it WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it +WARNING: skipping "vacowned_part1" --- only table or database owner can analyze it ANALYZE vacowned_part1; WARNING: skipping "vacowned_part1" --- only table or database owner can analyze it ANALYZE vacowned_part2; WARNING: skipping "vacowned_part2" --- only table or database owner can analyze it VACUUM (ANALYZE) vacowned_parted; -WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it WARNING: skipping "vacowned_part2" --- only table or database owner can vacuum it +WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it VACUUM (ANALYZE) vacowned_part1; WARNING: skipping "vacowned_part1" --- only table or database owner can vacuum it VACUUM (ANALYZE) vacowned_part2;