diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index e9a31aa257..8d80efd445 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1308,8 +1308,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 48377ace24..d640aa4bda 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 @@ -1586,13 +1586,19 @@ build_reloptions(Datum reloptions, bool validate, bytea * partitioned_table_reloptions(Datum reloptions, bool validate) { + static const relopt_parse_elt tab[] = { + {"autovacuum_enabled", RELOPT_TYPE_BOOL, + offsetof(PartitionedTableOptions, autovacuum_enabled)} + }; + /* * There are no options for partitioned tables yet, but this is able to do * some validation. */ return (bytea *) build_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED, - 0, NULL, 0); + sizeof(PartitionedTableOptions), + tab, lengthof(tab)); } /* diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index c1dd8168ca..7bd4950d90 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" @@ -2036,11 +2037,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 @@ -2063,7 +2064,12 @@ do_autovacuum(void) bool wraparound; if (classForm->relkind != RELKIND_RELATION && - classForm->relkind != RELKIND_MATVIEW) + classForm->relkind != RELKIND_MATVIEW && + classForm->relkind != RELKIND_PARTITIONED_TABLE) + continue; + + /* Collect partitioned tables, not partitions. So skip them. */ + if (classForm->relispartition) continue; relid = classForm->oid; @@ -2092,19 +2098,85 @@ do_autovacuum(void) continue; } - /* Fetch reloptions and the pgstat entry for this table */ - relopts = extract_autovac_opts(tuple, pg_class_desc); - tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared, - shared, dbentry); + if (classForm->relkind == RELKIND_RELATION || + classForm->relkind == RELKIND_MATVIEW) + { + /* Fetch reloptions and the pgstat entry for this table */ + relopts = extract_autovac_opts(tuple, pg_class_desc); + tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared, + shared, dbentry); + + /* Check if it needs vacuum or analyze */ + relation_needs_vacanalyze(relid, relopts, classForm, tabentry, + effective_multixact_freeze_max_age, + &dovacuum, &doanalyze, &wraparound); + + /* Relations that need work are added to table_oids */ + if (dovacuum || doanalyze) + table_oids = lappend_oid(table_oids, relid); + } + else + { + /* + * If the relation is a partitioned table, we check if its children + * need vacuum or analyze. All children excluding foreign partitions + * need to do that are added to the table_oids list. At least one + * child is added the list, the partitioned table become an object + * for autovacuum. + */ + List *tableOIDs; + ListCell *lc; + List *child_oids = NIL; + bool av_enabled; - /* Check if it needs vacuum or analyze */ - relation_needs_vacanalyze(relid, relopts, classForm, tabentry, - effective_multixact_freeze_max_age, - &dovacuum, &doanalyze, &wraparound); + /* + * Fetch reloptions and check whether partitioned table needs + * autovacuum or not. + */ + relopts = extract_autovac_opts(tuple, pg_class_desc); + av_enabled = (relopts ? relopts->enabled : true); - /* Relations that need work are added to table_oids */ - if (dovacuum || doanalyze) - table_oids = lappend_oid(table_oids, relid); + /* Find all members of inheritance set taking AccessShareLock */ + tableOIDs = find_all_inheritors(relid, AccessShareLock, NULL); + + foreach(lc, tableOIDs) + { + Oid childOID = lfirst_oid(lc); + HeapTuple childtuple; + Form_pg_class childclassForm; + + /* Ignore the parent table */ + if (childOID == relid) + continue; + + childtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(childOID)); + childclassForm = (Form_pg_class) GETSTRUCT(childtuple); + + /* Skip foreign partitions */ + if (childclassForm->relkind == RELKIND_FOREIGN_TABLE) + continue; + + /* Fetch reloptions and the pgstat entry for this table */ + relopts = extract_autovac_opts(childtuple, pg_class_desc); + tabentry = get_pgstat_tabentry_relid(childOID, + childclassForm->relisshared, + shared, dbentry); + + relation_needs_vacanalyze(childOID, relopts, childclassForm, tabentry, + effective_multixact_freeze_max_age, + &dovacuum, &doanalyze, &wraparound); + + if (dovacuum || doanalyze) + child_oids = lappend_oid(child_oids, childOID); + } + + if (child_oids) + { + if (av_enabled) + table_oids = lappend_oid(table_oids, relid); + table_oids = list_concat(table_oids, child_oids); + } + } /* * Remember TOAST associations for the second pass. Note: we must do @@ -2725,6 +2797,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); @@ -2798,33 +2871,79 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, return NULL; classForm = (Form_pg_class) GETSTRUCT(classTup); - /* - * Get the applicable reloptions. If it is a TOAST table, try to get the - * main table reloptions if the toast table itself doesn't have. - */ - avopts = extract_autovac_opts(classTup, pg_class_desc); - if (classForm->relkind == RELKIND_TOASTVALUE && - avopts == NULL && table_toast_map != NULL) + if (classForm->relkind != RELKIND_PARTITIONED_TABLE) { - av_relation *hentry; - bool found; + /* + * Get the applicable reloptions. If it is a TOAST table, try to get the + * main table reloptions if the toast table itself doesn't have. + */ + avopts = extract_autovac_opts(classTup, pg_class_desc); + if (classForm->relkind == RELKIND_TOASTVALUE && + avopts == NULL && table_toast_map != NULL) + { + av_relation *hentry; + bool found; + + hentry = hash_search(table_toast_map, &relid, HASH_FIND, &found); + if (found && hentry->ar_hasrelopts) + avopts = &hentry->ar_reloptions; + } + + /* fetch the pgstat table entry */ + tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared, + shared, dbentry); - hentry = hash_search(table_toast_map, &relid, HASH_FIND, &found); - if (found && hentry->ar_hasrelopts) - avopts = &hentry->ar_reloptions; + relation_needs_vacanalyze(relid, avopts, classForm, tabentry, + effective_multixact_freeze_max_age, + &dovacuum, &doanalyze, &wraparound); + + /* ignore ANALYZE for toast tables */ + if (classForm->relkind == RELKIND_TOASTVALUE) + doanalyze = false; } + else + { + /* + * If the relation is partitioned and doesn't have any foreign tables + * we check its children again. + */ + List *tableOIDs; + ListCell *lc; - /* fetch the pgstat table entry */ - tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared, - shared, dbentry); + /* Find all members of inheritance set taking AccessShareLock */ + tableOIDs = find_all_inheritors(relid, AccessShareLock, NULL); + + foreach(lc, tableOIDs) + { + Oid childOID = lfirst_oid(lc); + HeapTuple childtuple; + Form_pg_class childclassForm; + + /* Ignore the parent table */ + if (childOID == relid) + continue; - relation_needs_vacanalyze(relid, avopts, classForm, tabentry, - effective_multixact_freeze_max_age, - &dovacuum, &doanalyze, &wraparound); + childtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(childOID)); + childclassForm = (Form_pg_class) GETSTRUCT(childtuple); - /* ignore ANALYZE for toast tables */ - if (classForm->relkind == RELKIND_TOASTVALUE) - doanalyze = false; + /* Skip foreign partitions */ + if (childclassForm->relkind == RELKIND_FOREIGN_TABLE) + continue; + + /* Fetch reloptions and the pgstat entry */ + avopts = extract_autovac_opts(childtuple, pg_class_desc); + tabentry = get_pgstat_tabentry_relid(childOID, childclassForm->relisshared, + shared, dbentry); + + relation_needs_vacanalyze(childOID, avopts, childclassForm, tabentry, + effective_multixact_freeze_max_age, + &dovacuum, &doanalyze, &wraparound); + + /* Its parents need vacuum or analyze */ + if (dovacuum || doanalyze) + break; + } + } /* OK, it needs something done */ if (doanalyze || dovacuum) diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 31d8a1a10e..d007d7feab 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -326,6 +326,16 @@ typedef struct StdRdOptions ((relation)->rd_options ? \ ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw)) +/* + * PartitionedTableOptions + * Contents of rd_options for partitioned tables + */ +typedef struct PartitionedTableOptions +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + bool autovacuum_enabled; +} PartitionedTableOptions; + /* ViewOptions->check_option values */ typedef enum ViewOptCheckOption {