You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by li...@apache.org on 2017/10/03 19:38:21 UTC
spark git commit: [SPARK-21644][SQL] LocalLimit.maxRows is defined
incorrectly
Repository: spark
Updated Branches:
refs/heads/master fa225da74 -> 4c5158eec
[SPARK-21644][SQL] LocalLimit.maxRows is defined incorrectly
## What changes were proposed in this pull request?
The definition of `maxRows` in `LocalLimit` operator was simply wrong. This patch introduces a new `maxRowsPerPartition` method and uses that in pruning. The patch also adds more documentation on why we need local limit vs global limit.
Note that this previously has never been a bug because the way the code is structured, but future use of the maxRows could lead to bugs.
## How was this patch tested?
Should be covered by existing test cases.
Closes #18851
Author: gatorsmile <ga...@gmail.com>
Author: Reynold Xin <rx...@databricks.com>
Closes #19393 from gatorsmile/pr-18851.
Project: http://git-wip-us.apache.org/repos/asf/spark/repo
Commit: http://git-wip-us.apache.org/repos/asf/spark/commit/4c5158ee
Tree: http://git-wip-us.apache.org/repos/asf/spark/tree/4c5158ee
Diff: http://git-wip-us.apache.org/repos/asf/spark/diff/4c5158ee
Branch: refs/heads/master
Commit: 4c5158eec9101ef105274df6b488e292a56156a2
Parents: fa225da
Author: Reynold Xin <rx...@databricks.com>
Authored: Tue Oct 3 12:38:13 2017 -0700
Committer: gatorsmile <ga...@gmail.com>
Committed: Tue Oct 3 12:38:13 2017 -0700
----------------------------------------------------------------------
.../sql/catalyst/optimizer/Optimizer.scala | 29 +++++++-----
.../catalyst/plans/logical/LogicalPlan.scala | 5 ++
.../plans/logical/basicLogicalOperators.scala | 49 +++++++++++++++++++-
.../sql/execution/basicPhysicalOperators.scala | 3 ++
4 files changed, 74 insertions(+), 12 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/spark/blob/4c5158ee/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala
----------------------------------------------------------------------
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala
index b9fa39d..bc2d4a8 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala
@@ -305,13 +305,20 @@ object LimitPushDown extends Rule[LogicalPlan] {
}
}
- private def maybePushLimit(limitExp: Expression, plan: LogicalPlan): LogicalPlan = {
- (limitExp, plan.maxRows) match {
- case (IntegerLiteral(maxRow), Some(childMaxRows)) if maxRow < childMaxRows =>
+ private def maybePushLocalLimit(limitExp: Expression, plan: LogicalPlan): LogicalPlan = {
+ (limitExp, plan.maxRowsPerPartition) match {
+ case (IntegerLiteral(newLimit), Some(childMaxRows)) if newLimit < childMaxRows =>
+ // If the child has a cap on max rows per partition and the cap is larger than
+ // the new limit, put a new LocalLimit there.
LocalLimit(limitExp, stripGlobalLimitIfPresent(plan))
+
case (_, None) =>
+ // If the child has no cap, put the new LocalLimit.
LocalLimit(limitExp, stripGlobalLimitIfPresent(plan))
- case _ => plan
+
+ case _ =>
+ // Otherwise, don't put a new LocalLimit.
+ plan
}
}
@@ -323,7 +330,7 @@ object LimitPushDown extends Rule[LogicalPlan] {
// pushdown Limit through it. Once we add UNION DISTINCT, however, we will not be able to
// pushdown Limit.
case LocalLimit(exp, Union(children)) =>
- LocalLimit(exp, Union(children.map(maybePushLimit(exp, _))))
+ LocalLimit(exp, Union(children.map(maybePushLocalLimit(exp, _))))
// Add extra limits below OUTER JOIN. For LEFT OUTER and FULL OUTER JOIN we push limits to the
// left and right sides, respectively. For FULL OUTER JOIN, we can only push limits to one side
// because we need to ensure that rows from the limited side still have an opportunity to match
@@ -335,19 +342,19 @@ object LimitPushDown extends Rule[LogicalPlan] {
// - If neither side is limited, limit the side that is estimated to be bigger.
case LocalLimit(exp, join @ Join(left, right, joinType, _)) =>
val newJoin = joinType match {
- case RightOuter => join.copy(right = maybePushLimit(exp, right))
- case LeftOuter => join.copy(left = maybePushLimit(exp, left))
+ case RightOuter => join.copy(right = maybePushLocalLimit(exp, right))
+ case LeftOuter => join.copy(left = maybePushLocalLimit(exp, left))
case FullOuter =>
(left.maxRows, right.maxRows) match {
case (None, None) =>
if (left.stats.sizeInBytes >= right.stats.sizeInBytes) {
- join.copy(left = maybePushLimit(exp, left))
+ join.copy(left = maybePushLocalLimit(exp, left))
} else {
- join.copy(right = maybePushLimit(exp, right))
+ join.copy(right = maybePushLocalLimit(exp, right))
}
case (Some(_), Some(_)) => join
- case (Some(_), None) => join.copy(left = maybePushLimit(exp, left))
- case (None, Some(_)) => join.copy(right = maybePushLimit(exp, right))
+ case (Some(_), None) => join.copy(left = maybePushLocalLimit(exp, left))
+ case (None, Some(_)) => join.copy(right = maybePushLocalLimit(exp, right))
}
case _ => join
http://git-wip-us.apache.org/repos/asf/spark/blob/4c5158ee/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/LogicalPlan.scala
----------------------------------------------------------------------
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/LogicalPlan.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/LogicalPlan.scala
index 68aae72..1418882 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/LogicalPlan.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/LogicalPlan.scala
@@ -98,6 +98,11 @@ abstract class LogicalPlan
def maxRows: Option[Long] = None
/**
+ * Returns the maximum number of rows this plan may compute on each partition.
+ */
+ def maxRowsPerPartition: Option[Long] = maxRows
+
+ /**
* Returns true if this expression and all its children have been resolved to a specific schema
* and false if it still contains any unresolved placeholders. Implementations of LogicalPlan
* can override this (e.g.
http://git-wip-us.apache.org/repos/asf/spark/blob/4c5158ee/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/basicLogicalOperators.scala
----------------------------------------------------------------------
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/basicLogicalOperators.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/basicLogicalOperators.scala
index f443cd5..80243d3 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/basicLogicalOperators.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/basicLogicalOperators.scala
@@ -191,6 +191,9 @@ object Union {
}
}
+/**
+ * Logical plan for unioning two plans, without a distinct. This is UNION ALL in SQL.
+ */
case class Union(children: Seq[LogicalPlan]) extends LogicalPlan {
override def maxRows: Option[Long] = {
if (children.exists(_.maxRows.isEmpty)) {
@@ -200,6 +203,17 @@ case class Union(children: Seq[LogicalPlan]) extends LogicalPlan {
}
}
+ /**
+ * Note the definition has assumption about how union is implemented physically.
+ */
+ override def maxRowsPerPartition: Option[Long] = {
+ if (children.exists(_.maxRowsPerPartition.isEmpty)) {
+ None
+ } else {
+ Some(children.flatMap(_.maxRowsPerPartition).sum)
+ }
+ }
+
// updating nullability to make all the children consistent
override def output: Seq[Attribute] =
children.map(_.output).transpose.map(attrs =>
@@ -669,6 +683,27 @@ case class Pivot(
}
}
+/**
+ * A constructor for creating a logical limit, which is split into two separate logical nodes:
+ * a [[LocalLimit]], which is a partition local limit, followed by a [[GlobalLimit]].
+ *
+ * This muds the water for clean logical/physical separation, and is done for better limit pushdown.
+ * In distributed query processing, a non-terminal global limit is actually an expensive operation
+ * because it requires coordination (in Spark this is done using a shuffle).
+ *
+ * In most cases when we want to push down limit, it is often better to only push some partition
+ * local limit. Consider the following:
+ *
+ * GlobalLimit(Union(A, B))
+ *
+ * It is better to do
+ * GlobalLimit(Union(LocalLimit(A), LocalLimit(B)))
+ *
+ * than
+ * Union(GlobalLimit(A), GlobalLimit(B)).
+ *
+ * So we introduced LocalLimit and GlobalLimit in the logical plan node for limit pushdown.
+ */
object Limit {
def apply(limitExpr: Expression, child: LogicalPlan): UnaryNode = {
GlobalLimit(limitExpr, LocalLimit(limitExpr, child))
@@ -682,6 +717,11 @@ object Limit {
}
}
+/**
+ * A global (coordinated) limit. This operator can emit at most `limitExpr` number in total.
+ *
+ * See [[Limit]] for more information.
+ */
case class GlobalLimit(limitExpr: Expression, child: LogicalPlan) extends UnaryNode {
override def output: Seq[Attribute] = child.output
override def maxRows: Option[Long] = {
@@ -692,9 +732,16 @@ case class GlobalLimit(limitExpr: Expression, child: LogicalPlan) extends UnaryN
}
}
+/**
+ * A partition-local (non-coordinated) limit. This operator can emit at most `limitExpr` number
+ * of tuples on each physical partition.
+ *
+ * See [[Limit]] for more information.
+ */
case class LocalLimit(limitExpr: Expression, child: LogicalPlan) extends UnaryNode {
override def output: Seq[Attribute] = child.output
- override def maxRows: Option[Long] = {
+
+ override def maxRowsPerPartition: Option[Long] = {
limitExpr match {
case IntegerLiteral(limit) => Some(limit)
case _ => None
http://git-wip-us.apache.org/repos/asf/spark/blob/4c5158ee/sql/core/src/main/scala/org/apache/spark/sql/execution/basicPhysicalOperators.scala
----------------------------------------------------------------------
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/basicPhysicalOperators.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/basicPhysicalOperators.scala
index 8389e2f..63cd169 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/basicPhysicalOperators.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/basicPhysicalOperators.scala
@@ -554,6 +554,9 @@ case class RangeExec(range: org.apache.spark.sql.catalyst.plans.logical.Range)
/**
* Physical plan for unioning two plans, without a distinct. This is UNION ALL in SQL.
+ *
+ * If we change how this is implemented physically, we'd need to update
+ * [[org.apache.spark.sql.catalyst.plans.logical.Union.maxRowsPerPartition]].
*/
case class UnionExec(children: Seq[SparkPlan]) extends SparkPlan {
override def output: Seq[Attribute] =
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org