-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-15752] [SQL] Optimize metadata only query that has an aggregate whose children are deterministic project or filter operators. #13494
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
2ca2c38
edea710
8426522
153293e
7dfb743
68e6d6d
595ef36
7d7ece0
2e55a9d
b2b6eba
c5a291e
6404c1f
1bb5812
7e3729e
fbf5d61
3411fd6
aefab7f
c5ccdea
ae6cf9f
159331b
3a1438b
c0a7d59
a4045ca
0a023e7
a9b38ab
a5ea995
1bed08d
a22e962
41fef2c
bd53678
88f7308
2568193
26a97f4
4297f9f
1a65aa7
d5e0df4
9d6dd76
9cb01d8
3e2687d
2b4faf3
88fd3bf
a894bb7
9546b40
85b695b
bcfe8e5
67211be
501f93b
8ee2a8c
d888c85
ff16509
358ad13
030776a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,11 +28,10 @@ import org.apache.spark.sql.execution.datasources.{HadoopFsRelation, LogicalRela | |
|
|
||
| /** | ||
| * When scanning only partition columns, get results based on metadata without scanning files. | ||
| * It is used for distinct, distinct aggregations or distinct-like aggregations(example: Max/Min). | ||
| * First of all, scanning only partition columns are required, then the rule does the following | ||
| * things here: | ||
| * It's used for operators that only need distinct values. Currently only [[Aggregate]] operator | ||
| * which satisfy the following conditions are supported: | ||
| * 1. aggregate expression is partition columns, | ||
| * e.g. SELECT col FROM tbl GROUP BY col or SELECT col FROM tbl GROUP BY cube(col). | ||
| * e.g. SELECT col FROM tbl GROUP BY col, SELECT col FROM tbl GROUP BY cube(col). | ||
| * 2. aggregate function on partition columns with DISTINCT, | ||
| * e.g. SELECT count(DISTINCT col) FROM tbl GROUP BY col. | ||
| * 3. aggregate function on partition columns which have same result with DISTINCT keyword. | ||
|
|
@@ -43,36 +42,40 @@ case class MetadataOnlyOptimizer( | |
| catalog: SessionCatalog) extends Rule[LogicalPlan] { | ||
|
|
||
| private def canSupportMetadataOnly(a: Aggregate): Boolean = { | ||
|
||
| val aggregateExpressions = a.aggregateExpressions.flatMap { expr => | ||
| expr.collect { | ||
| case agg: AggregateExpression => agg | ||
| } | ||
| }.distinct | ||
| if (aggregateExpressions.isEmpty) { | ||
| // Support for aggregate that has no aggregateFunction when expressions are partition columns | ||
| // example: select partitionCol from table group by partitionCol. | ||
| // Moreover, multiple-distinct has been rewritted into it by RewriteDistinctAggregates. | ||
| true | ||
| if (!a.references.forall(_.isPartitionColumn)) { | ||
| // Support for scanning only partition columns | ||
| false | ||
| } else { | ||
| aggregateExpressions.forall { agg => | ||
| if (agg.isDistinct) { | ||
| true | ||
| } else { | ||
| // If function can be evaluated on just the distinct values of a column, it can be used | ||
| // by metadata-only optimizer. | ||
| agg.aggregateFunction match { | ||
| case max: Max => true | ||
| case min: Min => true | ||
| case hyperLog: HyperLogLogPlusPlus => true | ||
| case _ => false | ||
| val aggregateExpressions = a.aggregateExpressions.flatMap { expr => | ||
| expr.collect { | ||
| case agg: AggregateExpression => agg | ||
| } | ||
| }.distinct | ||
| if (aggregateExpressions.isEmpty) { | ||
| // Support for aggregate that has no aggregateFunction when expressions are partition | ||
| // columns. example: select partitionCol from table group by partitionCol. | ||
| // Moreover, multiple-distinct has been rewritted into it by RewriteDistinctAggregates. | ||
| true | ||
| } else { | ||
| aggregateExpressions.forall { agg => | ||
| if (agg.isDistinct) { | ||
| true | ||
| } else { | ||
| // If function can be evaluated on just the distinct values of a column, it can be used | ||
| // by metadata-only optimizer. | ||
| agg.aggregateFunction match { | ||
| case max: Max => true | ||
| case min: Min => true | ||
| case hyperLog: HyperLogLogPlusPlus => true | ||
| case _ => false | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private def convertLogicalToMetadataOnly( | ||
| project: LogicalPlan, | ||
| filter: Option[Expression], | ||
| logical: LogicalRelation, | ||
| files: HadoopFsRelation): LogicalPlan = { | ||
|
|
@@ -81,80 +84,62 @@ case class MetadataOnlyOptimizer( | |
| attributeMap.getOrElse(field.name, throw new AnalysisException( | ||
| s"Unable to resolve ${field.name} given [${logical.output.map(_.name).mkString(", ")}]")) | ||
| } | ||
| val projectSet = filter.map(project.references ++ _.references).getOrElse(project.references) | ||
| if (projectSet.subsetOf(AttributeSet(partitionColumns))) { | ||
| val selectedPartitions = files.location.listFiles(filter.map(Seq(_)).getOrElse(Seq.empty)) | ||
| val valuesRdd = sparkSession.sparkContext.parallelize(selectedPartitions.map(_.values), 1) | ||
| val valuesPlan = LogicalRDD(partitionColumns, valuesRdd)(sparkSession) | ||
| valuesPlan | ||
| } else { | ||
| logical | ||
| } | ||
| val selectedPartitions = files.location.listFiles(filter.map(Seq(_)).getOrElse(Seq.empty)) | ||
| val valuesRdd = sparkSession.sparkContext.parallelize(selectedPartitions.map(_.values), 1) | ||
| val valuesPlan = LogicalRDD(partitionColumns, valuesRdd)(sparkSession) | ||
| valuesPlan | ||
| } | ||
|
|
||
| private def convertCatalogToMetadataOnly( | ||
| project: LogicalPlan, | ||
| filter: Option[Expression], | ||
| relation: CatalogRelation): LogicalPlan = { | ||
| private def convertCatalogToMetadataOnly(relation: CatalogRelation): LogicalPlan = { | ||
| val attributeMap = relation.output.map(attr => (attr.name, attr)).toMap | ||
| val partitionColumns = relation.catalogTable.partitionColumnNames.map { column => | ||
| attributeMap.getOrElse(column, throw new AnalysisException( | ||
| s"Unable to resolve ${column} given [${relation.output.map(_.name).mkString(", ")}]")) | ||
| } | ||
| val projectSet = filter.map(project.references ++ _.references).getOrElse(project.references) | ||
| if (projectSet.subsetOf(AttributeSet(partitionColumns))) { | ||
| val partitionColumnDataTypes = partitionColumns.map(_.dataType) | ||
| val partitionValues = catalog.listPartitions(relation.catalogTable.identifier) | ||
| .map { p => | ||
| InternalRow.fromSeq( | ||
| partitionColumns.map(a => p.spec(a.name)).zip(partitionColumnDataTypes).map { | ||
| case (rawValue, dataType) => Cast(Literal(rawValue), dataType).eval(null) | ||
| }) | ||
| } | ||
| val valuesRdd = sparkSession.sparkContext.parallelize(partitionValues, 1) | ||
| val valuesPlan = LogicalRDD(partitionColumns, valuesRdd)(sparkSession) | ||
| valuesPlan | ||
| } else { | ||
| relation | ||
| } | ||
| val partitionColumnDataTypes = partitionColumns.map(_.dataType) | ||
| val partitionValues = catalog.listPartitions(relation.catalogTable.identifier) | ||
| .map { p => | ||
| InternalRow.fromSeq( | ||
| partitionColumns.map(a => p.spec(a.name)).zip(partitionColumnDataTypes).map { | ||
| case (rawValue, dataType) => Cast(Literal(rawValue), dataType).eval(null) | ||
| }) | ||
| } | ||
| val valuesRdd = sparkSession.sparkContext.parallelize(partitionValues, 1) | ||
| val valuesPlan = LogicalRDD(partitionColumns, valuesRdd)(sparkSession) | ||
| valuesPlan | ||
| } | ||
|
|
||
| private def convertToMetadataOnly(plan: LogicalPlan): LogicalPlan = plan match { | ||
|
||
| case p @ Project(fields, child) => | ||
| case p @ Project(fields, child) if p.references.forall(_.isPartitionColumn) => | ||
| child match { | ||
| case f @ Filter(condition, l @ LogicalRelation(files: HadoopFsRelation, _, _)) | ||
| if files.partitionSchema.nonEmpty => | ||
| val plan = convertLogicalToMetadataOnly(p, Some(condition), l, files) | ||
| if files.partitionSchema.nonEmpty && f.references.forall(_.isPartitionColumn) => | ||
| val plan = convertLogicalToMetadataOnly(Some(condition), l, files) | ||
| p.withNewChildren(f.withNewChildren(plan :: Nil) :: Nil) | ||
|
|
||
| case l @ LogicalRelation(files: HadoopFsRelation, _, _) | ||
| if files.partitionSchema.nonEmpty => | ||
| val plan = convertLogicalToMetadataOnly(p, None, l, files) | ||
| val plan = convertLogicalToMetadataOnly(None, l, files) | ||
| p.withNewChildren(plan :: Nil) | ||
|
|
||
| case f @ Filter(condition, relation: CatalogRelation) | ||
| if relation.catalogTable.partitionColumnNames.nonEmpty => | ||
| val plan = convertCatalogToMetadataOnly(p, Some(condition), relation) | ||
| if relation.catalogTable.partitionColumnNames.nonEmpty && | ||
| f.references.forall(_.isPartitionColumn) => | ||
| val plan = convertCatalogToMetadataOnly(relation) | ||
| p.withNewChildren(f.withNewChildren(plan :: Nil) :: Nil) | ||
|
|
||
| case relation: CatalogRelation | ||
| if relation.catalogTable.partitionColumnNames.nonEmpty => | ||
| val plan = convertCatalogToMetadataOnly(p, None, relation) | ||
| val plan = convertCatalogToMetadataOnly(relation) | ||
| p.withNewChildren(plan :: Nil) | ||
|
|
||
| case other => | ||
| p.withNewChildren(p.children.map(convertToMetadataOnly(_))) | ||
| other | ||
| } | ||
|
|
||
| case f : Filter => | ||
| f.withNewChildren(f.children.map(convertToMetadataOnly(_))) | ||
|
|
||
| case e: Expand => | ||
| e.withNewChildren(e.children.map(convertToMetadataOnly(_))) | ||
|
|
||
| case u: Union => | ||
| u.withNewChildren(u.children.map(convertToMetadataOnly(_))) | ||
|
|
||
| case other: LogicalPlan => | ||
| other | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this example is wrong, we can not aggregate on grouping columns, it should be
SELECT count(DISTINCT col1) FROM tbl GROUP BY col2