Skip to content

Commit 5b0ee1b

Browse files
author
Rafa de la Torre
authored
Merge pull request #10 from Algunenano/REL_10_1_create_table_patch
Allow DML commands that create tables to use parallel query.
2 parents dd860ee + 60657d1 commit 5b0ee1b

File tree

10 files changed

+151
-28
lines changed

10 files changed

+151
-28
lines changed

doc/src/sgml/parallel.sgml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,10 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
151151
<para>
152152
The query writes any data or locks any database rows. If a query
153153
contains a data-modifying operation either at the top level or within
154-
a CTE, no parallel plans for that query will be generated. This is a
155-
limitation of the current implementation which could be lifted in a
156-
future release.
154+
a CTE, no parallel plans for that query will be generated. As an
155+
exception, the commands <literal>CREATE TABLE</>, <literal>SELECT
156+
INTO</>, and <literal>CREATE MATERIALIZED VIEW</> which create a new
157+
table and populate it can use a parallel plan.
157158
</para>
158159
</listitem>
159160

@@ -241,15 +242,6 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
241242
</para>
242243
</listitem>
243244

244-
<listitem>
245-
<para>
246-
A prepared statement is executed using a <literal>CREATE TABLE .. AS
247-
EXECUTE ..</literal> statement. This construct converts what otherwise
248-
would have been a read-only operation into a read-write operation,
249-
making it ineligible for parallel query.
250-
</para>
251-
</listitem>
252-
253245
<listitem>
254246
<para>
255247
The transaction isolation level is serializable. This situation

src/backend/access/heap/heapam.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2581,15 +2581,17 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
25812581
CommandId cid, int options)
25822582
{
25832583
/*
2584-
* For now, parallel operations are required to be strictly read-only.
2585-
* Unlike heap_update() and heap_delete(), an insert should never create a
2586-
* combo CID, so it might be possible to relax this restriction, but not
2587-
* without more thought and testing.
2588-
*/
2589-
if (IsInParallelMode())
2584+
* Parallel operations are required to be strictly read-only in a parallel
2585+
* worker. Parallel inserts are not safe even in the leader in the
2586+
* general case, because group locking means that heavyweight locks for
2587+
* relation extension or GIN page locks will not conflict between members
2588+
* of a lock group, but we don't prohibit that case here because there are
2589+
* useful special cases that we can safely allow, such as CREATE TABLE AS.
2590+
*/
2591+
if (IsParallelWorker())
25902592
ereport(ERROR,
25912593
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
2592-
errmsg("cannot insert tuples during a parallel operation")));
2594+
errmsg("cannot insert tuples in a parallel worker")));
25932595

25942596
if (relation->rd_rel->relhasoids)
25952597
{

src/backend/commands/createas.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,8 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
326326
query = linitial_node(Query, rewritten);
327327
Assert(query->commandType == CMD_SELECT);
328328

329-
/* plan the query --- note we disallow parallelism */
330-
plan = pg_plan_query(query, 0, params);
329+
/* plan the query */
330+
plan = pg_plan_query(query, CURSOR_OPT_PARALLEL_OK, params);
331331

332332
/*
333333
* Use a snapshot with an updated command ID to ensure this query sees

src/backend/commands/explain.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -400,16 +400,14 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
400400
* We have to rewrite the contained SELECT and then pass it back to
401401
* ExplainOneQuery. It's probably not really necessary to copy the
402402
* contained parsetree another time, but let's be safe.
403-
*
404-
* Like ExecCreateTableAs, disallow parallelism in the plan.
405403
*/
406404
CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
407405
List *rewritten;
408406

409407
rewritten = QueryRewrite(castNode(Query, copyObject(ctas->query)));
410408
Assert(list_length(rewritten) == 1);
411409
ExplainOneQuery(linitial_node(Query, rewritten),
412-
0, ctas->into, es,
410+
CURSOR_OPT_PARALLEL_OK, ctas->into, es,
413411
queryString, params, queryEnv);
414412
}
415413
else if (IsA(utilityStmt, DeclareCursorStmt))

src/backend/executor/execMain.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,11 +1697,9 @@ ExecutePlan(EState *estate,
16971697

16981698
/*
16991699
* If the plan might potentially be executed multiple times, we must force
1700-
* it to run without parallelism, because we might exit early. Also
1701-
* disable parallelism when writing into a relation, because no database
1702-
* changes are allowed in parallel mode.
1700+
* it to run without parallelism, because we might exit early.
17031701
*/
1704-
if (!execute_once || dest->mydest == DestIntoRel)
1702+
if (!execute_once)
17051703
use_parallel_mode = false;
17061704

17071705
estate->es_use_parallel_mode = use_parallel_mode;

src/backend/optimizer/plan/planner.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,16 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
257257
* to values that don't permit parallelism, or if parallel-unsafe
258258
* functions are present in the query tree.
259259
*
260+
* (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
261+
* MATERIALIZED VIEW to use parallel plans, but this is safe only because
262+
* the command is writing into a completely new table which workers won't
263+
* be able to see. If the workers could see the table, the fact that
264+
* group locking would cause them to ignore the leader's heavyweight
265+
* relation extension lock and GIN page locks would make this unsafe.
266+
* We'll have to fix that somehow if we want to allow parallel inserts in
267+
* general; updates and deletes have additional problems especially around
268+
* combo CIDs.)
269+
*
260270
* For now, we don't try to use parallel mode if we're running inside a
261271
* parallel worker. We might eventually be able to relax this
262272
* restriction, but for now it seems best not to have parallel workers
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
--
2+
-- PARALLEL
3+
--
4+
-- Serializable isolation would disable parallel query, so explicitly use an
5+
-- arbitrary other level.
6+
begin isolation level repeatable read;
7+
-- encourage use of parallel plans
8+
set parallel_setup_cost=0;
9+
set parallel_tuple_cost=0;
10+
set min_parallel_table_scan_size=0;
11+
set max_parallel_workers_per_gather=4;
12+
--
13+
-- Test write operations that has an underlying query that is eligble
14+
-- for parallel plans
15+
--
16+
explain (costs off) create table parallel_write as
17+
select length(stringu1) from tenk1 group by length(stringu1);
18+
QUERY PLAN
19+
---------------------------------------------------
20+
Finalize HashAggregate
21+
Group Key: (length((stringu1)::text))
22+
-> Gather
23+
Workers Planned: 4
24+
-> Partial HashAggregate
25+
Group Key: length((stringu1)::text)
26+
-> Parallel Seq Scan on tenk1
27+
(7 rows)
28+
29+
create table parallel_write as
30+
select length(stringu1) from tenk1 group by length(stringu1);
31+
drop table parallel_write;
32+
explain (costs off) select length(stringu1) into parallel_write
33+
from tenk1 group by length(stringu1);
34+
QUERY PLAN
35+
---------------------------------------------------
36+
Finalize HashAggregate
37+
Group Key: (length((stringu1)::text))
38+
-> Gather
39+
Workers Planned: 4
40+
-> Partial HashAggregate
41+
Group Key: length((stringu1)::text)
42+
-> Parallel Seq Scan on tenk1
43+
(7 rows)
44+
45+
select length(stringu1) into parallel_write
46+
from tenk1 group by length(stringu1);
47+
drop table parallel_write;
48+
explain (costs off) create materialized view parallel_mat_view as
49+
select length(stringu1) from tenk1 group by length(stringu1);
50+
QUERY PLAN
51+
---------------------------------------------------
52+
Finalize HashAggregate
53+
Group Key: (length((stringu1)::text))
54+
-> Gather
55+
Workers Planned: 4
56+
-> Partial HashAggregate
57+
Group Key: length((stringu1)::text)
58+
-> Parallel Seq Scan on tenk1
59+
(7 rows)
60+
61+
create materialized view parallel_mat_view as
62+
select length(stringu1) from tenk1 group by length(stringu1);
63+
drop materialized view parallel_mat_view;
64+
prepare prep_stmt as select length(stringu1) from tenk1 group by length(stringu1);
65+
explain (costs off) create table parallel_write as execute prep_stmt;
66+
QUERY PLAN
67+
---------------------------------------------------
68+
Finalize HashAggregate
69+
Group Key: (length((stringu1)::text))
70+
-> Gather
71+
Workers Planned: 4
72+
-> Partial HashAggregate
73+
Group Key: length((stringu1)::text)
74+
-> Parallel Seq Scan on tenk1
75+
(7 rows)
76+
77+
create table parallel_write as execute prep_stmt;
78+
drop table parallel_write;
79+
rollback;

src/test/regress/parallel_schedule

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ test: rules psql_crosstab amutils
9696

9797
# run by itself so it can run parallel workers
9898
test: select_parallel
99+
test: write_parallel
99100

100101
# no relation related tests can be put in this group
101102
test: publication subscription

src/test/regress/serial_schedule

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ test: stats_ext
134134
test: rules
135135
test: psql_crosstab
136136
test: select_parallel
137+
test: write_parallel
137138
test: publication
138139
test: subscription
139140
test: amutils
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--
2+
-- PARALLEL
3+
--
4+
5+
-- Serializable isolation would disable parallel query, so explicitly use an
6+
-- arbitrary other level.
7+
begin isolation level repeatable read;
8+
9+
-- encourage use of parallel plans
10+
set parallel_setup_cost=0;
11+
set parallel_tuple_cost=0;
12+
set min_parallel_table_scan_size=0;
13+
set max_parallel_workers_per_gather=4;
14+
15+
--
16+
-- Test write operations that has an underlying query that is eligble
17+
-- for parallel plans
18+
--
19+
explain (costs off) create table parallel_write as
20+
select length(stringu1) from tenk1 group by length(stringu1);
21+
create table parallel_write as
22+
select length(stringu1) from tenk1 group by length(stringu1);
23+
drop table parallel_write;
24+
25+
explain (costs off) select length(stringu1) into parallel_write
26+
from tenk1 group by length(stringu1);
27+
select length(stringu1) into parallel_write
28+
from tenk1 group by length(stringu1);
29+
drop table parallel_write;
30+
31+
explain (costs off) create materialized view parallel_mat_view as
32+
select length(stringu1) from tenk1 group by length(stringu1);
33+
create materialized view parallel_mat_view as
34+
select length(stringu1) from tenk1 group by length(stringu1);
35+
drop materialized view parallel_mat_view;
36+
37+
prepare prep_stmt as select length(stringu1) from tenk1 group by length(stringu1);
38+
explain (costs off) create table parallel_write as execute prep_stmt;
39+
create table parallel_write as execute prep_stmt;
40+
drop table parallel_write;
41+
42+
rollback;

0 commit comments

Comments
 (0)