calcite 入门(Calcite的基本概念)
calcite 入门(Calcite的基本概念)代表表的数据定义,对应表的数据列名和类型。public interface Table { RelDataType getRowType(RelDataTypeFactory typeFactory); Statistic getStatistic(); Schema.TableType getJdbcTableType(); boolean isRolledUp(String column); boolean rolledUpColumnValidInsideAgg(String column SqlCall call SqlNode parent CalciteConnectionConfig config); }RelDataType3.1 catalogSchemapublic interface Schema { Table g
背景Apache Calcite是一个面向Hadoop新的查询引擎,它提供了标准的SQL语言,多种查询和优化。calcite之前叫做optiq,是作为Hive的SQL引擎,为hive提供成本规模的优化功能即CBO (Cost Based Optimizatio) 2014年5月作为独立孵化项目开源。关于Calcite 的架构有三个特点:flexible embeddable and extensible,就是灵活性、组件可插拔、可扩展,它的 SQL Parser 层、Optimizer 层等都可以单独使用,这也是 Calcite 受总多开源框架欢迎的原因之一。
Calcite的架构关于calcite的架构,相对于传统数据库,他将数据存储和数据处理算法和元信息存储忽略了,这样使得calcite更加兼容,适合统一查询服务。
由上图可以看到,calcite最核心的就是optimizer优化器,一个optimizer engine包含三个组成部分:
- rules:也就是匹配规则,calcite目前内置上百种rule来优化relations expression 也支持自定义的rules
- Metadata providers:主要向优化器提供信息 有助于指导优化器向着目标(减少整体cost)进行优化,信息可以包括行数,table哪一列是唯一列,也包括计算relNode树,执行subexpression cost的函数。
- Planner engines:触发rules来达到指定目标,比如cost-base expression optimizer(CBO)的目标是减少cost(减少数据行数,CPU cost IO cost等)。
对SQL执行完整流程,分为4部分
- Parser: 解析,calcite通过javaCC将SQL解析成未经校验的AST(Abstract Syntax Tree 即抽象语法树)。
- Validate: 验证,主要负责校验上一步parser是否合法,如验证SQL的schema信息,字段,函数等是否存在,SQL是否合法,此步骤完成后生成RealNode。
- Optimize:优化,也是整个核心所在,将逻辑计划RelNode转化为物理计划,主要涉及SQL优化:基于规则优化(RBO),基于代价优化(CBO) optimizer按道理是可以选的。此步骤得到物理计划
- Execute:执行阶段,将物理计划转成程序,变成自己平台的可执行代码。
3.1 catalog
- schema: 主要定义schema与表的结合,有点像database,但是sechma不一定是强制的,比如同名表T1就需要加上A.T1 B.T1.
- table:对应数据库的表
- RelDataType:代表表的数据定义,对应表的数据列名和类型。
Schema
public interface Schema {
Table getTable(String name);
Set<String> getTableNames();
RelProtoDataType getType(String name);
Set<String> getTypeNames();
Collection<Function> getFunctions(String name);
Set<String> getFunctionNames();
Schema getSubSchema(String name);
Set<String> getSubSchemaNames();
Expression getExpression(SchemaPlus parentSchema String name);
boolean isMutable();
Schema snapshot(SchemaVersion version);
/** Table type. */
enum TableType {}
}
Table
public interface Table {
RelDataType getRowType(RelDataTypeFactory typeFactory);
Statistic getStatistic();
Schema.TableType getJdbcTableType();
boolean isRolledUp(String column);
boolean rolledUpColumnValidInsideAgg(String column SqlCall call
SqlNode parent CalciteConnectionConfig config);
}
RelDataType
代表表的数据定义,对应表的数据列名和类型。
3.2 SQL Parse(SQL -> SQL Node)
Calcite使用javaCC作为SQL解析,javaCC通过Calcite中定义的Parse.jj文件,生成一系列的java代码,生成的代码会把SQL转换成AST的数据结构(SQL Node类型)
与 Javacc 相似的工具还有 ANTLR,JavaCC 中的 jj 文件也跟 ANTLR 中的 G4文件类似,Apache Spark 中使用这个工具做类似的事情。
javaCC要实现一个SQL parser,他的功能有两点,这里都需要在 jj 文件中定义
- 设计词法和语义,定义SQL中的具体元素。
- 实现词法分析器(Lexer)和语法分析器(Parse) 完成对SQL的解析,完成相应的转换。
//org.apache.calcite.prepare.CalcitePrepareImpl
//...
//解析sql->SqlNode
SqlParser parser = createParser(query.sql parserConfig);
SqlNode sqlNode;
try {
sqlNode = parser.parseStmt();
statementType = getStatementType(sqlNode.getKind());
} catch (SqlParseException e) {
throw new RuntimeException(
"parse failed: " e.getMessage() e);
}
//...
经过上述代码sql经过parser.parseStmt解析之后,会生成一个SqlNode Debug后的对象如图所示
3.3 验证和转换 (SQL Node -> RelNode)
经过上面的解析,会将SQL翻译成SQL 的抽象语法树 AST SQL Node就是抽象语法树的节点Node 而Rel代表关系表达式(Relation Expression) 所以从sql node -> rel node 的过程,是一个转换,一个校验的过程。
//org.apache.calcite.prepare. Prepare
//function prepareSql
RelRoot root =
sqlToRelConverter.convertQuery(sqlQuery needsValidation true);
3.3.1 校验Validate
语法检查前我们要先知道元信息,这个检查会包括表名,字段名,字段类型,函数名,进行语法检查的实现如下:
//org.apache.calcite.sql.validate.SqlValidatorImpl
public SqlNode validate(SqlNode topNode) {
SqlValidatorScope scope = new EmptyScope(this);
scope = new CatalogScope(scope ImmutableList.of("CATALOG"));
final SqlNode topNode2 = validateScopedExpression(topNode scope);
final RelDataType type = getValidatedNodeType(topNode2);
Util.discard(type);
return topNode2;
}
在对SQL node的校验过程中,会对AST的语法进行补充,例如:
//校验前
select province avg(cast(age as double)) from t_user group by province
//校验后
SELECT `T_USER`.`PROVINCE` AVG(CAST(`T_USER`.`AGE` AS DOUBLE))
FROM `TEST_PRO`.`T_USER` AS `T_USER`
GROUP BY `T_USER`.`PROVINCE`
3.3.2 SQL Node->Rel Node
在经过上面进行合法的补充了一定语义的AST树之后,而validate中已经准备好各种映射信息后,就开始具体的RelNode的转换了。
//org.apache.calcite.sql2rel.SqlToRelConverter
protected RelRoot convertQueryRecursive(SqlNode query boolean top
RelDataType targetRowType) {
final SqlKind kind = query.getKind();
switch (kind) {
case SELECT:
return RelRoot.of(convertSelect((SqlSelect) query top) kind);
case INSERT:
return RelRoot.of(convertInsert((SqlInsert) query) kind);
case DELETE:
return RelRoot.of(convertDelete((SqlDelete) query) kind);
case UPDATE:
return RelRoot.of(convertUpdate((SqlUpdate) query) kind);
case MERGE:
return RelRoot.of(convertMerge((SqlMerge) query) kind);
case UNION:
case INTERSECT:
case EXCEPT:
return RelRoot.of(convertSetOp((SqlCall) query) kind);
case WITH:
return convertWith((SqlWith) query top);
case VALUES:
return RelRoot.of(convertValues((SqlCall) query targetRowType) kind);
default:
throw new AssertionError("not a query: " query);
}
}
测试SQL
select province avg(cast(age as double)) from t_user group by province
得到的语法树
Plan after converting SqlNode to RelNode
LogicalAggregate(group=[{0}] EXPR$1=[AVG($1)])
LogicalProject(PROVINCE=[$3] $f1=[CAST($2):DOUBLE])
LogicalTableScan(table=[[TEST_PRO T_USER]])
3.4 优化 Optimizer
在calcite中有两种优化器
- HepPlanner(RBO):它是一个启发式的优化器,按照规则进行匹配,知道达到次数限制,或者遍历一遍,不在出现rule match为止。
- VolcanoPlanner(RBO CBO):他会一直迭代rule 知道找到最小的cost的plan
这里看下calcite 优化器的实现,RelOptPlanner是Calcite的基类,其子类实现如下图所示:
3.4.1 基于规则的优化(RBO)
基于规则的优化器(Rule-Based Optimizer,RBO):根据优化规则对关系表达式进行转换,这里说的是,一个表达式A经过优化 得到另一个表达式B,得到B的同时A被裁掉,经过一系列转换得到最终的结果。
RBO有着一套严格顺序的优化规则,同样一条SQL,无论读取表中数据是什么样的,最后生成的执行计划都是相同的,所以SQL的性能可能和SQL本身的书写有很大的关系。
3.4.2 基于代价的优化 (CBO)
基于代价的优化器(Cost-Based Optimizer,CBO):也是根据优化规则对表达式进行转换,这里不同的是,表达式A在转换表达式B后,会被保存下来,根据不同的规则生成多个不同的表达式,CBO会根据统计模型和代价模型,在多个表达式选择一个cost最小的计划作为整个执行计划。
由上可知,CBO有两个依赖,一个是统计模型,一个是代价模型,目前大多数的计算引擎都趋向CBO,但是CBO比较有难度,因为不能明确预知数据量大小,所以CBO主要用于离线的场景。
感谢您的阅读,如果喜欢本文欢迎关注和转发,本头条号将坚持持续分享IT技术知识。对于文章内容有其他想法或意见建议等,欢迎提出共同讨论共同进步。
原文地址:
https://lixiyan4633.gitee.io/2020/03/18/calcite教程01-calcite的基本概念/