presto下推优化

2,001 阅读2分钟

什么是下推

比如以下的sql

select name from mysql.config.user_info where id = 123

简单看起来,将sql直接交给mysql执行,presto拿最终的返回结果就行了。

不过,presto最大的特性在于跨源,不同数据源的sql语法可能会有细微差异(比如在支持的函数上)。所以,presto需要实现自己的一套sql语法,直接将sql交给mysql肯定是不行了。

而且,在跨源场景下的一些复杂sql(比如不同数据源里表的join),肯定是需要对sql进行解析,并将不同的部分交给不同的数据源去执行的。

比如以下的sql,涉及两个mysql实例,从mysql1里和mysql2里关联查数据。

select a.id, a.count
from
  (select id, count
  from mysql1.statis.table_a
  where count > 100) a
  left join
  (select id
  from mysql2.config.table_b
  where create_time >= '2021-01-01') b
on a.id = b.id
where b.id is null

对于该sql,有两种执行的想法。如下图所示。

左边的是一个带着优化思路的正常想法。而右边的思路,对于sql解析的程序实现上,却是较为规范统一的。

实际上,presto是先按照右图的思路,根据sql生成执行计划。此时,where count > 100仍然是在presto服务上执行的。之后,presto会对执行计划进行优化,将where count > 100推给mysql执行。这就是所谓的“下推”了。

下推之后,presto的实际执行其实就符合左图的思路了。其实,下推的不仅仅是where count > 100,还包含select id, count。这样,mysql就只需要返回过滤后的id和count,而不是所有字段。

下推的源码执行

sql的执行简单说起来可以是这样

  1. coordinator将presto的sql解析成一系列的内部数据(如语法树、执行计划)
  2. worker在加载mysql数据的时候,又根据这些内部数据重新拼接成mysql的sql,交给mysql去执行

我们以最开始的这个简单例子,粗略看下有关下推的代码执行(依据的presto版本是0.213,比较老,只挑选了部分代码以简化展示)。

select name from mysql.config.user_info where id = 123
  1. 生成和优化执行计划
//presto-main
class LogicalPlanner {
  public Plan plan(Analysis analysis, Stage stage) {
    PlanNode root = planStatement(analysis, analysis.getStatement()); //根据语法树生成执行计划
    for (PlanOptimizer optimizer : planOptimizers) { //遍历所有的优化器,优化执行计划
      root = optimizer.optimize(root, ...);
    }
}

优化前root包含的TableScanNode节点如下:

优化后root包含的TableScanNode节点如下:

可以看到,优化是会直接修改执行计划树的。

  1. 在worker上组装sql
//presto-base-jdbc
class QueryBuilder {
  public PreparedStatement buildSql(...) {
  
    StringBuilder sql = new StringBuilder()
    sql.append("SELECT ")
    ...
    List<String> clauses = toConjuncts(columns, tupleDomain, accumulator) //这里的tupleDomain就是执行计划树root中的那个tupleDomain
    if (!clauses.isEmpty()) {
      sql.append(" WHERE ").append(Joiner.on(" AND ").join(clauses))
    }
    PreparedStatement statement = client.getPreparedStatement(connection, sql.toString())
    statement.setInt(...)
  }
}