openLooKeng新下推框架介绍和实践
openLooKeng执行计划优化简介
在讲述下推框架之前,让我们先来简单介绍一下openLooKeng执行计划优化的大致流程。
如上图所示,接收到用户SQL语句之后,SQL被转换成一个Abstract syntax tree (AST)树。AST树再被转换成逻辑执行计划树。然后,也就是执行计划优化过程中最重要的一步,使用规则(Rule)或者优化器(Optimizers)进行执行计划优化,每一个PlanOptimizer可以操作一个子执行计划树,PlanOptimizer基于统计或者经验,用一个更优的子执行计划替换当前的子树,达到优化的目的。PlanOptimizers通常是长期的经验累积得出来的一些优化规则,比如谓词下推、join reorder等等。PlanOptimizers可以存储一些物理执行信息在ConnectorHandle中。新下推框架则工作在这一层。
得到最优的执行计划之后,逻辑执行计划被转换成物理执行计划,然后被分片,分割成按照stage执行的一个个子树,最终调度到worker上执行。
PrestoSql下推方案介绍
最初,openLooKeng是从PrestoSql演进而来,在其基础上增加了很多功能特性,以及大量的性能优化。在讲述openLooKeng的新下推框架之前,先大概介绍其原下推方案。
根据社区的讨论,PrestoSql原下推框架有一些目标:
- 使用现有的Rule或Optimizer的框架,而且不是使用基于visitor模式的PlanOptimizers来实现下推,同时能够让connector提供转换rules来实现下推;
- 并不是建立一个原生的机制来支持所有操作的下推。
先来看看prestoSql的执行过程:
- 首先,引入一些列的下推规则,每一个规则负责下推相应的操作到TableScan操作中,比如PushFilterIntoConnector, PushProjectionIntoConnector, PushAggregationIntoConnector等等;
- 上述的这些rules通过指定的metadata调用与connectors交互,如果connector支持这个操作下推,操作则被下推到TableScan操作,同时在connectorTableHadle中记录相关信息。
下面以PushFilterIntoConnector为例说明。
在上述的例子中,假设filter中有两个过滤条件,一个是like,一个是f函数,其中connector能处理like表达式。
PushFilterIntoConnector会调用Metadata.pushFilter,实际上是调用connector的pushFilter函数,这个函数会返回一个新的tableHandle,新的tableHandle中记录了like表达式的相关信息,同时返回一个remaining filter,即connector不能处理的表达式。最终,PushFilterIntoConnector就把原来的执行计划(上图中第一个框)转换成一个新的执行计划(上图中最后一个框)。
上述的基于Rule的下推方案存在以下的几个问题
- 不能JoinNode,WindowNode等Nodes的下推,特别是 join的情况,join node不仅仅需要visit当前的join node,还需要visit他的左右节点,同时,还需要保存join的上下文信息,基于rule的下推方案难以处理这种情况;
- 下推逻辑复杂,下推上下文信息无法保存,对于Join的情况,join的下推信息不知道存储在哪。
基于上述原因,当前的下推方案不能把sql语句下推到数据源操作中,这样对于那些执行速度相当快的数据源就不能充分发挥数据源本身的能力,所以引入了新的下推框架。
openLooKeng新下推框架
思想
openLooKeng新下推框架的主要思想是把执行计划子树暴露给connector,让connector提供PlanOptimizers(基于visitor模式的)给执行优化引擎,这样可以让connector引入任意的优化。
为了防止一个connector的PlanOptimizers修改其他connector的执行计划子树,openLooKeng对于暴露给Connector的PlanNode做了两个限制:(1)暴露出来的PlanNodes须移动到presto-spi模块;(2)仅仅暴露属于connector的子执行计划树给相应的connector。如下图所示,左子树只会暴露给Hive Connector,右子树只会暴露给Mysql Connector。然后会应用他们各自的PlanOptimizers。
实现
openLooKeng的下推框架如上图所示,新下推框架的工作原理很简单,主要分为两步:(1)Connector在启动的时候会告诉执行优化引擎其提供的ConnectorPlanOptimizer,如下图的HiveFpuPushdownOptimizer,其需要实现上图的optimize接口,optimize函数以子执行计划为入口,返回优化后的执行计划;(2)在执行优化引擎中引入ApplyConnectorOptimization优化器,该Optimizer会把根据子执行计划所在的connector,调用其connectorPlanOptimizer。如下图所示,经过HiveFpuPushdownOptimizer优化之后,Aggregation和Filter操作都下推到了数据源中。
修改
新框架特性PR链接为https://gitee.com/openlookeng/hetu-core/pulls/633,主要做了如下修改:
- 移动PlanNodes到presto-spi模块
- 修改PlanNode和Assignments中的Expression为RowExpression(下一节描述)
- 添加TranslateExpressions 和ApplyConnectorOptimization Optimizer
- 修改已经存在的Rules和Optimizers
- 为connector添加ConnectorPlanOptimizer
Expression-to-RowExpression
在数据库或者查询系统中,为了更好的隔离,AST树和IR树是隔离,他们使用的数据结构也不一样,这也就是presto社区讨论的分离AST(Node)和IR(PlanNode)。具体就是指把AST树种的Expression转换成PlanNode中的RowExpression。当前AST和PlanNode在混用Expression,不能做到很好的隔离。
当前一个执行计划的生命周期如下:
- building AST
- building raw plan
- plan optimization
- plan sanity check
- plan cost computation
- building subplans
- distributing subplan (over the wire)
- compiling subplan locally
当前,Expression到RowExpression的转换发生在第8步。我们把转换这个操作移到了第3步。之所以没有把没有把转换移到第二步,是因为涉及的面太广了,修改量太大了.
Example演示
首先,在演示系统中配置了三个catalog,他们都指向同一个数据源,不过下推的设置不一样,mysql2不下退,mysql1部分下推(join不下推),mysql全下推。
不下推的情况
部分下推的情况,在这个例子中,filter下推了
全部下推情况
如何贡献
下面简单介绍一下开发者如何适配新的下推框架,即在新增的connector中,如何添加connectorPlanOptimizer。主要步骤如下:
第一:在XXXConnector中复写下面的函数
第二:实现XXXPlanOptimizerProvider
第三:在XXXConnector中实现PlanOptimizer
第四:实现PlanOptimizer里面的optimize函数,主要是实现一个visitor去visit执行计划树
第五:实现Visitor,用来生成下推的语句,同时修改执行计划树
第六:实现XXXQueryGenerator,在XXXQueryGenerator中实现一个visitor用来把下推的信息记录到XXXQueryGeneratorContext,如果存在节点可以下推,则生成对应的sql
详细的实现可以参考openLookeng的baseJdbc的实现,实现了JDBC数据源的下推。
- 点赞
- 收藏
- 关注作者
评论(0)