From da17ee93e5031156bb07a2ef940a7d3dc95b43c7 Mon Sep 17 00:00:00 2001 From: Seamus Abshere Date: Mon, 15 Feb 2021 10:34:32 -0500 Subject: [PATCH v2] Allow setting parallel_workers on partitioned tables. Sometimes it's beneficial to do ALTER TABLE my_part_tbl SET (parallel_workers = 32); when the query planner is planning too few workers to read from your partitions. For example, if you have a partitioned table where each partition is a foreign table that cannot itself have this setting on it. Shown to give a 100%+ performance increase (in my case, 700%) when used with cstore_fdw column store partitions. --- doc/src/sgml/ref/create_table.sgml | 14 +-- src/backend/access/common/reloptions.c | 14 ++- src/backend/optimizer/path/allpaths.c | 114 +++++++++++++------------ src/include/utils/rel.h | 10 +++ 4 files changed, 90 insertions(+), 62 deletions(-) diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 3b2b227683..2ed1150928 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1337,8 +1337,9 @@ 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. + Specifying most of these parameters for partitioned tables is not + supported, but you may specify them for individual leaf partitions; + refer to the description of individual parameters for more details. @@ -1401,9 +1402,12 @@ WITH ( MODULUS numeric_literal, REM This sets the number of workers that should be used to assist a parallel scan of this table. If not set, the system will determine a value based - on the relation size. The actual number of workers chosen by the planner - or by utility statements that use parallel scans may be less, for example - due to the setting of . + on the relation size. When set on a partitioned table, the specified + number of workers will work on distinct partitions, so the number of + partitions affected by the parallel operation should be taken into + account. The actual number of workers chosen by the planner or by + utility statements that use parallel scans may be less, for example due + to the setting of . diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index c687d3ee9e..f8443d2361 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -377,7 +377,7 @@ static relopt_int intRelOpts[] = { "parallel_workers", "Number of parallel processes that can be used per executor node for this relation.", - RELOPT_KIND_HEAP, + RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED, ShareUpdateExclusiveLock }, -1, 0, 1024 @@ -1962,12 +1962,18 @@ bytea * partitioned_table_reloptions(Datum reloptions, bool validate) { /* - * There are no options for partitioned tables yet, but this is able to do - * some validation. + * Currently the only setting known to be useful for partitioned tables + * is parallel_workers. */ + static const relopt_parse_elt tab[] = { + {"parallel_workers", RELOPT_TYPE_INT, + offsetof(PartitionedTableRdOptions, parallel_workers)}, + }; + return (bytea *) build_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED, - 0, NULL, 0); + sizeof(PartitionedTableRdOptions), + tab, lengthof(tab)); } /* diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index cd3fdd259c..a0803cf955 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -1268,6 +1268,59 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, add_paths_to_append_rel(root, rel, live_childrels); } +/* + * compute_append_parallel_workers + * Computes the number of workers to assign to scan the subpaths appended + * by a given Append path + */ +static int +compute_append_parallel_workers(RelOptInfo *rel, List *subpaths, + int num_live_children, + bool parallel_append) +{ + ListCell *lc; + int parallel_workers = 0; + + /* + * For partitioned rels, first see if there is a root-level setting for + * parallel_workers. But only consider if a Parallel Append plan is + * to be considered. + */ + if (IS_PARTITIONED_REL(rel) && parallel_append) + parallel_workers = + compute_parallel_worker(rel, -1, -1, + max_parallel_workers_per_gather); + + /* Find the highest number of workers requested for any subpath. */ + foreach(lc, subpaths) + { + Path *path = lfirst(lc); + + parallel_workers = Max(parallel_workers, path->parallel_workers); + } + Assert(parallel_workers > 0 || subpaths == NIL); + + /* + * If the use of parallel append is permitted, always request at least + * log2(# of children) workers. We assume it can be useful to have + * extra workers in this case because they will be spread out across + * the children. The precise formula is just a guess, but we don't + * want to end up with a radically different answer for a table with N + * partitions vs. an unpartitioned table with the same data, so the + * use of some kind of log-scaling here seems to make some sense. + */ + if (parallel_append) + { + parallel_workers = Max(parallel_workers, + fls(num_live_children)); + parallel_workers = Min(parallel_workers, + max_parallel_workers_per_gather); + } + Assert(parallel_workers > 0); + + return parallel_workers; +} + /* * add_paths_to_append_rel @@ -1464,35 +1517,10 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, if (partial_subpaths_valid && partial_subpaths != NIL) { AppendPath *appendpath; - ListCell *lc; - int parallel_workers = 0; - - /* Find the highest number of workers requested for any subpath. */ - foreach(lc, partial_subpaths) - { - Path *path = lfirst(lc); - - parallel_workers = Max(parallel_workers, path->parallel_workers); - } - Assert(parallel_workers > 0); - - /* - * If the use of parallel append is permitted, always request at least - * log2(# of children) workers. We assume it can be useful to have - * extra workers in this case because they will be spread out across - * the children. The precise formula is just a guess, but we don't - * want to end up with a radically different answer for a table with N - * partitions vs. an unpartitioned table with the same data, so the - * use of some kind of log-scaling here seems to make some sense. - */ - if (enable_parallel_append) - { - parallel_workers = Max(parallel_workers, - fls(list_length(live_childrels))); - parallel_workers = Min(parallel_workers, - max_parallel_workers_per_gather); - } - Assert(parallel_workers > 0); + int parallel_workers = + compute_append_parallel_workers(rel, partial_subpaths, + list_length(live_childrels), + enable_parallel_append); /* Generate a partial append path. */ appendpath = create_append_path(root, rel, NIL, partial_subpaths, @@ -1519,30 +1547,10 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, if (pa_subpaths_valid && pa_nonpartial_subpaths != NIL) { AppendPath *appendpath; - ListCell *lc; - int parallel_workers = 0; - - /* - * Find the highest number of workers requested for any partial - * subpath. - */ - foreach(lc, pa_partial_subpaths) - { - Path *path = lfirst(lc); - - parallel_workers = Max(parallel_workers, path->parallel_workers); - } - - /* - * Same formula here as above. It's even more important in this - * instance because the non-partial paths won't contribute anything to - * the planned number of parallel workers. - */ - parallel_workers = Max(parallel_workers, - fls(list_length(live_childrels))); - parallel_workers = Min(parallel_workers, - max_parallel_workers_per_gather); - Assert(parallel_workers > 0); + int parallel_workers = + compute_append_parallel_workers(rel, pa_partial_subpaths, + list_length(live_childrels), + true); appendpath = create_append_path(root, rel, pa_nonpartial_subpaths, pa_partial_subpaths, diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 10b63982c0..fe114e0856 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -308,6 +308,16 @@ typedef struct StdRdOptions bool vacuum_truncate; /* enables vacuum to truncate a relation */ } StdRdOptions; +/* + * PartitionedTableRdOptions + * Contents of rd_options for partitioned tables + */ +typedef struct PartitionedTableRdOptions +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int parallel_workers; /* max number of parallel workers */ +} PartitionedTableRdOptions; + #define HEAP_MIN_FILLFACTOR 10 #define HEAP_DEFAULT_FILLFACTOR 100 -- 2.24.1