Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.catalyst.dsl.expressions._
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.catalyst.plans.QueryPlan
import org.apache.spark.sql.catalyst.plans.physical.{HashPartitioning, Partitioning}
import org.apache.spark.sql.catalyst.plans.physical.{HashPartitioning, Partitioning, RangePartitioning}
import org.apache.spark.sql.execution.{ColumnarBatchScan, LeafExecNode, SparkPlan, WholeStageCodegenExec}
import org.apache.spark.sql.execution.vectorized._
import org.apache.spark.sql.types._
Expand Down Expand Up @@ -170,6 +170,8 @@ case class InMemoryTableScanExec(
override def outputPartitioning: Partitioning = {
relation.cachedPlan.outputPartitioning match {
case h: HashPartitioning => updateAttribute(h).asInstanceOf[HashPartitioning]
case r: RangePartitioning =>
r.copy(ordering = r.ordering.map(updateAttribute(_).asInstanceOf[SortOrder]))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why RangePartitioning isn't included at first.

@mgaido91 mgaido91 Jun 14, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just updateAttribute(r)?

Moreover, in order to avoid the same issue in the future with other cases, have you considered doing something like:

updateAttribute(relation.cachedPlan.outputPartitioning)

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all Partitioning are Expression. Only HashPartitioning and RangePartitioning are.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, thanks @mgaido91.

@viirya Do we need consider below:
PartitioningCollection in InMemoryTableScanExec.outputPartitioning, which is also Expression?
PartitioningCollection and BroadcastPartitioning in ReusedExchangeExec.outputPartitioning?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, you're right @viirya , thanks. Then, I'd propose something like:

relation.cachedPlan.outputPartitioning match {
 case e: Expression => updateAttribute(e)
 case other => other
}

what do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think PartitioningCollection is for an operator that has multiple children. BroadcastPartitioning is not Expression.

@viirya viirya Jun 14, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, HashPartitioning and RangePartitioning can affect later sorting and shuffle. But for BroadcastPartitioning, seems to me no too much benefit.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PartitioningCollection should be considered. Like below case:

spark.conf.set("spark.sql.autoBroadcastJoinThreshold", -1)
spark.conf.set("spark.sql.codegen.wholeStage", false)
val df1 = Seq(1 -> "a", 3 -> "c", 2 -> "b").toDF("i", "j").as("t1")
val df2 = Seq(1 -> "a", 3 -> "c", 2 -> "b").toDF("m", "n").as("t2")
val d = df1.join(df2, $"t1.i" === $"t2.m")
d.cache
val d1 = d.as("t3")
val d2 = d.as("t4")
d1.join(d2, $"t3.i" === $"t4.i").explain
SortMergeJoin [i#5], [i#54], Inner
:- InMemoryTableScan [i#5, j#6, m#15, n#16]
:     +- InMemoryRelation [i#5, j#6, m#15, n#16], CachedRDDBuilder
:           +- SortMergeJoin [i#5], [m#15], Inner
:              :- Sort [i#5 ASC NULLS FIRST], false, 0
:              :  +- Exchange hashpartitioning(i#5, 10)
:              :     +- LocalTableScan [i#5, j#6]
:              +- Sort [m#15 ASC NULLS FIRST], false, 0
:                 +- Exchange hashpartitioning(m#15, 10)
:                    +- LocalTableScan [m#15, n#16]
+- Sort [i#54 ASC NULLS FIRST], false, 0
   +- Exchange hashpartitioning(i#54, 10)
      +- InMemoryTableScan [i#54, j#55, m#58, n#59]
            +- InMemoryRelation [i#54, j#55, m#58, n#59], CachedRDDBuilder
                  +- SortMergeJoin [i#5], [m#15], Inner
                     :- Sort [i#5 ASC NULLS FIRST], false, 0
                     :  +- Exchange hashpartitioning(i#5, 10)
                     :     +- LocalTableScan [i#5, j#6]
                     +- Sort [m#15 ASC NULLS FIRST], false, 0
                        +- Exchange hashpartitioning(m#15, 10)
                           +- LocalTableScan [m#15, n#16]

Exchange hashpartitioning(i#54, 10) is extra shuffle.

How do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For PartitioningCollection, I think it is harder to treat it like HashPartitioning and RangePartitioning when replacing attributes.

In above example, PartitioningCollection contains HashPartitioning(i#5) and HashPartitioning(m#15), the output of InMemoryRelation is [i#54, j#55, m#58, n#59]. Can we still replace attributes based on the location of attribute in output?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@viirya From updateAttribute, relation.cachedPlan.output and relation.output one to one.

 private def updateAttribute(expr: Expression): Expression = {
    ....
    val attrMap = AttributeMap(relation.cachedPlan.output.zip(relation.output))
    ....
  }

It means "[i#54, j#55, m#58, n#59]" corresponds to "[i#5, j#6, m#15, n#16]", so we can always replace HashPartitioning(i#5) to HashPartitioning(i#54).
Any idea?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct.

case _ => relation.cachedPlan.outputPartitioning
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import org.apache.spark.broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.catalyst.expressions.{Attribute, AttributeMap, Expression, SortOrder}
import org.apache.spark.sql.catalyst.plans.physical.{HashPartitioning, Partitioning}
import org.apache.spark.sql.catalyst.plans.physical.{HashPartitioning, Partitioning, RangePartitioning}
import org.apache.spark.sql.catalyst.rules.Rule
import org.apache.spark.sql.execution.{LeafExecNode, SparkPlan, UnaryExecNode}
import org.apache.spark.sql.internal.SQLConf
Expand Down Expand Up @@ -71,6 +71,8 @@ case class ReusedExchangeExec(override val output: Seq[Attribute], child: Exchan

override def outputPartitioning: Partitioning = child.outputPartitioning match {
case h: HashPartitioning => h.copy(expressions = h.expressions.map(updateAttr))
case r: RangePartitioning =>
r.copy(ordering = r.ordering.map(updateAttr(_).asInstanceOf[SortOrder]))
case other => other

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

}

Expand Down
11 changes: 11 additions & 0 deletions sql/core/src/test/scala/org/apache/spark/sql/DataFrameSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2270,4 +2270,15 @@ class DataFrameSuite extends QueryTest with SharedSQLContext {
val mapWithBinaryKey = map(lit(Array[Byte](1.toByte)), lit(1))
checkAnswer(spark.range(1).select(mapWithBinaryKey.getItem(Array[Byte](1.toByte))), Row(1))
}

test("SPARK-24556: ReusedExchange should rewrite output partitioning for RangePartitioning") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not an end-to-end test, let's put it in PlannerSuite and also test cached table.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please also mention cached table in PR title

val df = Seq(1 -> "a").toDF("i", "j")
val df1 = df.as("t1")
val df2 = df.as("t2")
val shuffles = df1.orderBy("j").join(df2.orderBy("j"), $"t1.i" === $"t2.i", "right")
.cache().orderBy($"t2.j").queryExecution.executedPlan.collect {
case e: ShuffleExchangeExec => e
}
assert(shuffles.isEmpty)
}
}