Push scan/join target list beneath Gather when possible.
authorRobert Haas <rhaas@postgresql.org>
Fri, 18 Mar 2016 13:46:40 +0000 (09:46 -0400)
committerRobert Haas <rhaas@postgresql.org>
Fri, 18 Mar 2016 13:50:05 +0000 (09:50 -0400)
This means that, for example, "SELECT expensive_func(a) FROM bigtab
WHERE something" can compute expensive_func(a) in the workers rather
than the leader if it happens to be parallel-safe, which figures to be
a big win in some practical cases.

Currently, we can only do this if the entire target list is
parallel-safe.  If we worked harder, we might be able to evaluate
parallel-safe targets in the worker and any parallel-restricted
targets in the leader, but that would be more complicated, and there
aren't that many parallel-restricted functions that people are likely
to use in queries anyway.  I think.  So just do the simple thing for
the moment.

Robert Haas, Amit Kapila, and Tom Lane

src/backend/optimizer/plan/createplan.c
src/backend/optimizer/util/pathnode.c

index e37bdfd2cfd16704dec49eb4c3900b2b7c55318b..f08f0ea01f627ee04998346cbfe63253a1f10fb8 100644 (file)
@@ -1396,18 +1396,21 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path)
    tlist = build_path_tlist(root, &best_path->path);
 
    /*
-    * Although the ProjectionPath node wouldn't have been made unless its
-    * pathtarget is different from the subpath's, it can still happen that
-    * the constructed tlist matches the subplan's.  (An example is that
-    * MergeAppend doesn't project, so we would have thought that we needed a
-    * projection to attach resjunk sort columns to its output ... but
-    * create_merge_append_plan might have added those same resjunk sort
-    * columns to both MergeAppend and its children.)  So, if the desired
-    * tlist is the same expression-wise as the subplan's, just jam it in
-    * there.  We'll have charged for a Result that doesn't actually appear in
-    * the plan, but that's better than having a Result we don't need.
+    * We might not really need a Result node here.  There are several ways
+    * that this can happen.  For example, MergeAppend doesn't project, so we
+    * would have thought that we needed a projection to attach resjunk sort
+    * columns to its output ... but create_merge_append_plan might have
+    * added those same resjunk sort columns to both MergeAppend and its
+    * children.  Alternatively, apply_projection_to_path might have created
+    * a projection path as the subpath of a Gather node even though the
+    * subpath was projection-capable.  So, if the subpath is capable of
+    * projection or the desired tlist is the same expression-wise as the
+    * subplan's, just jam it in there.  We'll have charged for a Result that
+    * doesn't actually appear in the plan, but that's better than having a
+    * Result we don't need.
     */
-   if (tlist_same_exprs(tlist, subplan->targetlist))
+   if (is_projection_capable_path(best_path->subpath) ||
+       tlist_same_exprs(tlist, subplan->targetlist))
    {
        plan = subplan;
        plan->targetlist = tlist;
index b8ea3168a84922225ca734c139d13e393aa470b8..541f7790ab0bb14ea56e994f931ff033b1cb4aba 100644 (file)
@@ -2222,6 +2222,36 @@ apply_projection_to_path(PlannerInfo *root,
    path->total_cost += target->cost.startup - oldcost.startup +
        (target->cost.per_tuple - oldcost.per_tuple) * path->rows;
 
+   /*
+    * If the path happens to be a Gather path, we'd like to arrange for the
+    * subpath to return the required target list so that workers can help
+    * project. But if there is something that is not parallel-safe in the
+    * target expressions, then we can't.
+    */
+   if (IsA(path, GatherPath) &&
+       !has_parallel_hazard((Node *) target->exprs, false))
+   {
+       GatherPath *gpath = (GatherPath *) path;
+
+       /*
+        * We always use create_projection_path here, even if the subpath is
+        * projection-capable, so as to avoid modifying the subpath in place.
+        * It seems unlikely at present that there could be any other
+        * references to the subpath anyway, but better safe than sorry.
+        * (create_projection_plan will only insert a Result node if the
+        * subpath is not projection-capable, so we only include the cost of
+        * that node if it will actually be inserted.  This is a bit grotty
+        * but we can improve it later if it seems important.)
+        */
+       if (!is_projection_capable_path(gpath->subpath))
+           gpath->path.total_cost += cpu_tuple_cost * gpath->subpath->rows;
+       gpath->subpath = (Path *)
+           create_projection_path(root,
+                                  gpath->subpath->parent,
+                                  gpath->subpath,
+                                  target);
+   }
+
    return path;
 }