Consider materializing the cheapest inner path in parallel nestloop
authorRichard Guo <[email protected]>
Fri, 12 Jul 2024 02:16:43 +0000 (11:16 +0900)
committerRichard Guo <[email protected]>
Fri, 12 Jul 2024 02:16:43 +0000 (11:16 +0900)
When generating non-parallel nestloop paths for each available outer
path, we always consider materializing the cheapest inner path if
feasible.  Similarly, in this patch, we also consider materializing
the cheapest inner path when building partial nestloop paths.  This
approach potentially reduces the need to rescan the inner side of a
partial nestloop path for each outer tuple.

Author: Tender Wang
Reviewed-by: Richard Guo, Robert Haas, David Rowley, Alena Rybakina
Reviewed-by: Tomasz Rybak, Paul Jungwirth, Yuki Fujii
Discussion: https://postgr.es/m/CAHewXNkPmtEXNfVQMou_7NqQmFABca9f4etjBtdbbm0ZKDmWvw@mail.gmail.com

src/backend/optimizer/path/joinpath.c
src/test/regress/expected/select_parallel.out
src/test/regress/sql/select_parallel.sql

index 40eb58341c1c4722ecb729f9525152f82eb57716..02dd9724924c775283ef93b07093fd0d558abb32 100644 (file)
@@ -2021,11 +2021,31 @@ consider_parallel_nestloop(PlannerInfo *root,
                           JoinPathExtraData *extra)
 {
    JoinType    save_jointype = jointype;
+   Path       *inner_cheapest_total = innerrel->cheapest_total_path;
+   Path       *matpath = NULL;
    ListCell   *lc1;
 
    if (jointype == JOIN_UNIQUE_INNER)
        jointype = JOIN_INNER;
 
+   /*
+    * Consider materializing the cheapest inner path, unless: 1) we're doing
+    * JOIN_UNIQUE_INNER, because in this case we have to unique-ify the
+    * cheapest inner path, 2) enable_material is off, 3) the cheapest inner
+    * path is not parallel-safe, 4) the cheapest inner path is parameterized
+    * by the outer rel, or 5) the cheapest inner path materializes its output
+    * anyway.
+    */
+   if (save_jointype != JOIN_UNIQUE_INNER &&
+       enable_material && inner_cheapest_total->parallel_safe &&
+       !PATH_PARAM_BY_REL(inner_cheapest_total, outerrel) &&
+       !ExecMaterializesOutput(inner_cheapest_total->pathtype))
+   {
+       matpath = (Path *)
+           create_material_path(innerrel, inner_cheapest_total);
+       Assert(matpath->parallel_safe);
+   }
+
    foreach(lc1, outerrel->partial_pathlist)
    {
        Path       *outerpath = (Path *) lfirst(lc1);
@@ -2082,6 +2102,11 @@ consider_parallel_nestloop(PlannerInfo *root,
                try_partial_nestloop_path(root, joinrel, outerpath, mpath,
                                          pathkeys, jointype, extra);
        }
+
+       /* Also consider materialized form of the cheapest inner path */
+       if (matpath != NULL)
+           try_partial_nestloop_path(root, joinrel, outerpath, matpath,
+                                     pathkeys, jointype, extra);
    }
 }
 
index c96285d1bb60e08c021ae5693146e021876743aa..7487ea0b820c5da5641c9d05b35ff15956809972 100644 (file)
@@ -653,6 +653,36 @@ select  count(*) from tenk1, tenk2 where tenk1.unique1 = tenk2.unique1;
 
 reset enable_hashjoin;
 reset enable_nestloop;
+-- test parallel nestloop join path with materialization of the inner path
+alter table tenk2 set (parallel_workers = 0);
+explain (costs off)
+   select * from tenk1 t1, tenk2 t2 where t1.two > t2.two;
+                QUERY PLAN                 
+-------------------------------------------
+ Gather
+   Workers Planned: 4
+   ->  Nested Loop
+         Join Filter: (t1.two > t2.two)
+         ->  Parallel Seq Scan on tenk1 t1
+         ->  Materialize
+               ->  Seq Scan on tenk2 t2
+(7 rows)
+
+-- the joinrel is not parallel-safe due to the OFFSET clause in the subquery
+explain (costs off)
+   select * from tenk1 t1, (select * from tenk2 t2 offset 0) t2 where t1.two > t2.two;
+                QUERY PLAN                 
+-------------------------------------------
+ Nested Loop
+   Join Filter: (t1.two > t2.two)
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Seq Scan on tenk1 t1
+   ->  Materialize
+         ->  Seq Scan on tenk2 t2
+(7 rows)
+
+alter table tenk2 reset (parallel_workers);
 -- test gather merge
 set enable_hashagg = false;
 explain (costs off)
index 20376c03fae2ea9f8a7f47f0c24843de771ad396..9b019d31ed71ed1a28f82c89c6f807013f759021 100644 (file)
@@ -266,6 +266,16 @@ select  count(*) from tenk1, tenk2 where tenk1.unique1 = tenk2.unique1;
 reset enable_hashjoin;
 reset enable_nestloop;
 
+-- test parallel nestloop join path with materialization of the inner path
+alter table tenk2 set (parallel_workers = 0);
+explain (costs off)
+   select * from tenk1 t1, tenk2 t2 where t1.two > t2.two;
+
+-- the joinrel is not parallel-safe due to the OFFSET clause in the subquery
+explain (costs off)
+   select * from tenk1 t1, (select * from tenk2 t2 offset 0) t2 where t1.two > t2.two;
+alter table tenk2 reset (parallel_workers);
+
 -- test gather merge
 set enable_hashagg = false;