Catalyst 优化逻辑执行计划规则

来源:互联网 发布:mac不进系统打开终端 编辑:程序博客网 时间:2024/05/18 01:52
http://blog.csdn.net/pelick/article/details/22723699

Optimizer

本文分析Catalyst Optimize部分实现的对逻辑执行计划(LogicalPlan)的处理规则


Optimizer处理的是LogicalPlan对象。

Optimizer的batches如下:
[java] view plain copy
  1. object Optimizer extends RuleExecutor[LogicalPlan] {  
  2.   val batches =  
  3.     Batch("ConstantFolding", Once,  
  4.       ConstantFolding, // 可静态分析的常量表达式  
  5.       BooleanSimplification, // 布尔表达式提前短路  
  6.       SimplifyFilters, // 简化过滤操作(false, true, null)  
  7.       SimplifyCasts) :: // 简化转换(对象所属类已经是Cast目标类)  
  8.     Batch("Filter Pushdown", Once,  
  9.       CombineFilters, // 相邻(上下级)Filter操作合并  
  10.       PushPredicateThroughProject, // 映射操作中的Filter谓词下推  
  11.       PushPredicateThroughInnerJoin) :: Nil // inner join操作谓词下推  
  12. }  

这是4.1号最新的Catalyst  Optimizer的代码。


ConstantFolding 

把可以静态分析出结果的表达式替换成Literal表达式。

[java] view plain copy
  1. object ConstantFolding extends Rule[LogicalPlan] {  
  2.   def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  3.     case q: LogicalPlan => q transformExpressionsDown {  
  4.       // Skip redundant folding of literals.  
  5.       case l: Literal => l  
  6.       case e if e.foldable => Literal(e.apply(null), e.dataType)  
  7.     }  
  8.   }  
  9. }  

Literal能处理的类型包括Int, Long, Double, Float, Byte,Short, String, Boolean, null。这些类型分别对应的是Catalyst框架的DataType,包括IntegerType, LongType, DoubleType,FloatType, ByteType, ShortType, StringType, BooleanType, NullType。

普通的Literal是不可变的,还有一个可变的MutalLiteral类,有update方法可以改变里面的value。


BooleanSimplification 

提前短路可以短路的布尔表达式

[java] view plain copy
  1. object BooleanSimplification extends Rule[LogicalPlan] {  
  2.   def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  3.     case q: LogicalPlan => q transformExpressionsUp {  
  4.       case and @ And(left, right) =>  
  5.         (left, right) match {  
  6.           case (Literal(true, BooleanType), r) => r  
  7.           case (l, Literal(true, BooleanType)) => l  
  8.           case (Literal(false, BooleanType), _) => Literal(false)  
  9.           case (_, Literal(false, BooleanType)) => Literal(false)  
  10.           case (_, _) => and  
  11.         }  
  12.   
  13.       case or @ Or(left, right) =>  
  14.         (left, right) match {  
  15.           case (Literal(true, BooleanType), _) => Literal(true)  
  16.           case (_, Literal(true, BooleanType)) => Literal(true)  
  17.           case (Literal(false, BooleanType), r) => r  
  18.           case (l, Literal(false, BooleanType)) => l  
  19.           case (_, _) => or  
  20.         }  
  21.     }  
  22.   }  
  23. }  

SimplifyFilters 

提前处理可以被判断的过滤操作

[java] view plain copy
  1. object SimplifyFilters extends Rule[LogicalPlan] {  
  2.   def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  3.     case Filter(Literal(true, BooleanType), child) =>  
  4.       child  
  5.     case Filter(Literal(null, _), child) =>  
  6.       LocalRelation(child.output)  
  7.     case Filter(Literal(false, BooleanType), child) =>  
  8.       LocalRelation(child.output)  
  9.   }  
  10. }  

SimplifyCasts 

把已经是目标类的Cast表达式替换掉

[java] view plain copy
  1. object SimplifyCasts extends Rule[LogicalPlan] {  
  2.   def apply(plan: LogicalPlan): LogicalPlan = plan transformAllExpressions {  
  3.     case Cast(e, dataType) if e.dataType == dataType => e  
  4.   }  
  5. }  

CombineFilters 

相邻都是过滤操作的话,把两个过滤操作合起来。相邻指的是上下两级。

[java] view plain copy
  1. object CombineFilters extends Rule[LogicalPlan] {  
  2.   def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  3.     case ff @ Filter(fc, nf @ Filter(nc, grandChild)) => Filter(And(nc, fc), grandChild)  
  4.   }  
  5. }  

PushPredicateThroughProject 

把Project操作中的过滤操作下推。这一步里顺带做了别名转换的操作(认为开销不大的前提下)。

[java] view plain copy
  1. object PushPredicateThroughProject extends Rule[LogicalPlan] {  
  2.   def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  3.     case filter @ Filter(condition, project @ Project(fields, grandChild)) =>  
  4.       val sourceAliases = fields.collect { case a @ Alias(c, _) =>  
  5.         (a.toAttribute: Attribute) -> c  
  6.       }.toMap // 把fields中的别名属性都取出来  
  7.       project.copy(child = filter.copy( // 生成新的Filter操作  
  8.         replaceAlias(condition, sourceAliases), // condition中有别名的替换掉  
  9.         grandChild))  
  10.   }  
  11.   
  12.   def replaceAlias(condition: Expression, sourceAliases: Map[Attribute, Expression]): Expression = {  
  13.     condition transform {  
  14.       case a: AttributeReference => sourceAliases.getOrElse(a, a)  
  15.     }  
  16.   }  
  17. }  

PushPredicateThroughInnerJoin 

先找到Filter操作,若Filter操作里面是一次inner join,那么先把Filter条件和inner join条件先全部取出来,

然后把只涉及到左侧或右侧的过滤操作下推到join外部,把剩下来不能下推的条件放到join操作的condition里。

[java] view plain copy
  1. object PushPredicateThroughInnerJoin extends Rule[LogicalPlan] with PredicateHelper {  
  2.   def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  3.     case f @ Filter(filterCondition, Join(left, right, Inner, joinCondition)) =>  
  4.       // 这一步是把过滤条件和join条件里的condition都提取出来  
  5.       val allConditions = splitConjunctivePredicates(filterCondition) ++  
  6.         joinCondition.map(splitConjunctivePredicates).getOrElse(Nil)  
  7.         
  8.       // 把参考属性都属于右侧输出属性的condition挑选到rightCondition里  
  9.       val (rightConditions, leftOrJoinConditions) =  
  10.         allConditions.partition(_.references subsetOf right.outputSet)  
  11.       // 同理,把剩余condition里面,参考属性都属于左侧输出属性的condition挑选到  
  12.       // leftCondition里,剩余的就属于joinCondition  
  13.       val (leftConditions, joinConditions) =  
  14.         leftOrJoinConditions.partition(_.references subsetOf left.outputSet)  
  15.   
  16.       // 生成新的left和right:先把condition里的操作用AND折叠起来,然后将该折叠后的表达式和原始的left/right logical plan合起来生成新的Filter操作,即新的Fil      // ter logical plan  
  17.       // 这样就做到了把过滤条件中的谓词下推到了left/right里,即本次inner join的“外部”  
  18.       val newLeft = leftConditions.reduceLeftOption(And).map(Filter(_, left)).getOrElse(left)  
  19.       val newRight = rightConditions.reduceLeftOption(And).map(Filter(_, right)).getOrElse(right)  
  20.       Join(newLeft, newRight, Inner, joinConditions.reduceLeftOption(And))  
  21.   }  
  22. }  

以下帮助理解上面这段代码。

Join操作(LogicalPlan的Binary)

[java] view plain copy
  1. case class Join(  
  2.   left: LogicalPlan,  
  3.   right: LogicalPlan,  
  4.   joinType: JoinType,  
  5.   condition: Option[Expression]) extends BinaryNode {  
  6.   
  7.   def references = condition.map(_.references).getOrElse(Set.empty)  
  8.   def output = left.output ++ right.output  
  9. }  

Filter操作(LogicalPlan的Unary)

[java] view plain copy
  1. case class Filter(condition: Expression, child: LogicalPlan) extends UnaryNode {  
  2.   def output = child.output  
  3.   def references = condition.references  
  4. }  

reduceLeftOption逻辑是这样的:

[java] view plain copy
  1. def reduceLeftOption[B >: A](op: (B, A) => B): Option[B] =  
  2.     if (isEmpty) None else Some(reduceLeft(op))  

reduceLeft(op)的结果是op( op( ... op(x_1, x_2) ...,x_{n-1}), x_n)


谓词助手这个trait,负责把And操作里的condition分离开,返回表达式Seq

[java] view plain copy
  1. trait PredicateHelper {  
  2.   def splitConjunctivePredicates(condition: Expression): Seq[Expression] = condition match {  
  3.     case And(cond1, cond2) => splitConjunctivePredicates(cond1) ++ splitConjunctivePredicates(cond2)  
  4.     case other => other :: Nil  
  5.   }  
  6. }  


Example

case class Person(name:String, age: Int)

case classNum(v1: Int, v2: Int)


case one

SELECT  people.age, num.v1,  num.v2

FROM

    people

    JOIN  num

    ON   people.age > 20  and  num.v1> 0

WHERE  num.v2< 50


== QueryPlan ==

Project [age#1:1,v1#2:2,v2#3:3]

CartesianProduct

      Filter(age#1:1 > 20)

          ExistingRdd[name#0,age#1], MappedRDD[4] at map at basicOperators.scala:124

      Filter((v2#3:1 < 50) && (v1#2:0 > 0))

          ExistingRdd [v1#2,v2#3],MappedRDD[10] at map at basicOperators.scala:124

 

分析:where条件 num.v2 < 50 下推到Join里


case two

SELECT people.age,  1+2

FROM

    people

    JOIN  num

    ON   people.name<>’abc’  and  num.v1> 0

WHERE num.v2 < 50

 

== QueryPlan ==

Project [age#1:1,3 AS c1#14]

    CartesianProduct

        Filter NOT(name#0:0 = abc)

            ExistingRdd[name#0,age#1], MappedRDD[4] at map at basicOperators.scala:124

        Filter((v2#3:1 < 50) && (v1#2:0 > 0))

            ExistingRdd[v1#2,v2#3], MappedRDD[10] at map at basicOperators.scala:124

 

分析:1+2 被提前常量折叠,并被取了一个别名


原创粉丝点击