Calcite的ModificationRel

目录

  • Calcite实现DML操作的概述
  • Calcite DML操作的详解
  • Calcite DML的性能及优化思路
  • 附录

Calcite实现DML操作的概述

Calcite使用javacc生成语法解析器,javacc和freemaker模版技术为解析器的自定义提供了可能,SQL语句经过解析后,生成SqlNode,然后通过convertQuery转换成RelNode。RelNode用于表达逻辑表达式,也用于优化执行树,最后的RelNode逻辑树生成后,转成Expression(Node),Expression具备转换成java代码的能力,然后再通过JinanoCompiler转换成字节码执行。这是三次解析一次编译的执行过程。

SqlNode转换成RelNode的时候,会补充上下文,所以RelNode包含丰富的相关信息,可用于执行计划的优化。但是RelNode转换成Expression时,很多上下文信息是无用的,会被丢弃。

Calcite内置了EnumerableConvention用于实现SQL的查询语句,Expression在转换成java代码时,使用Enumerable表达SQL语句的相关操作,对于不能全面支持SQL操作的adaptor,借助转换成Enumerable,是保证adaptor能全面支持SQL操作的关键。那么存储层只需要实现数据读写即可满足SQL的DML操作。

Calcite DML操作的详解

DML相关功能对应模块

概述环节对主要流程进行了描述,现对各流程中,DML操作的过程使用到的模块进行说明。

ModifiableTable赋予了Table查询和修改的能力,ModifiableTable继承QueryableTable,Queryable继承Enumerable,Queryable提供了数据的查询能力。也就是说,当需要select的时候,QueryableTable会被调用;而insert/update/delete时,ModifiableTable会被调用。

查询select的实现流程

在查询(select)时,有两种查询数据的方法,分别是:Schemas.queryable和Schemas.enumerable

Schemas.queryable会调用table.asQueryable,把需要查询的数据转换成Enumerable,这时的操作,需要table adaptor实现接口table.asQueryable

Schemas.enumerable会调用table.scan,把数据从table中读出来,这时的操作,需要table adaptor实现接口table.scan

具体采用哪种(queryable/enumerable)转换的方式,是在RelOptTableImpl.getClassExpressionFunction中实现的,做计划优化的时候(Prepare.optimize),RelNode.optimize会调用RelOptTableImpl.create,RelOptTableImpl.create会调用RelOptTableImpl.getClassExpressionFunction来完成转化,queryable此时调用getExpression作为参数传给RetOptTableImpl,而enumerable调用Schemas.tableExpression

修改操作的实现流程

在修改时,Insert语句从SqlNode转换到RelNode,再转换到Expression。

  • parseQuery

    • values(…)在sql parse的时候,会被转换成为row(…),表示一个结构体类型
  • convertQuery

    • validate,SqlValidatorImpl会执行操作值数量检查checkFieldCount、操作值类型检查checkTypeAssignment、约束检查checkConstraint、权限检查validateAccess,依赖adaptor getRowType的实现
    • convert,convertInsert会调用createModify,toModificationRel被调用,用于获取adaptor的上下文并保存到RelNode中。KtSQL只需要获得table的实例即可包含所需的上下文
    • optimize,convert返回的是LogicTableModify,经过optimize后,变成了EnumerableTableModify。optimize会把传进去的RelNode都转成EnumerableRel,才能和下一环节的implement对接
  • implement

    • EnumerableInterpretable.toBindable被调用,Calcite系统允许提供多个Implementor,且每个Implementor对节点的解析机制可以不一样,默认的实现是EnumerableInterpretable。通过遍历每一个节点的implement,生成Bindable返回。
    • RelNode需要被转换为Bindable执行,Bindable继承InterpretableRel,包含implement接口,实现该接口的Bindable,可以返回Java代码。EnumerableTableModify.implement就是代码生成的一个例子,根据需要调用的函数,输出相应的字符串。
    • Bindable的子类包括有:BindableAggregate, BindableFilter, BindableProject, BindableJoin, BindableSort, BindableTableScan, BindableUnion, BindableValues, BindableWindow, EnumerableBindable, EnumerableInterpretable. 所以可以需要实现代码输出的子类是可控的,可实现的。

toModificationRel

RelNode在SqlToRelConverter的过程中,需要根据SqlNode的输入,生成特定的RelNode,adaptor的接口,需要按RelNode的需要,提供相关的信息。遗憾的是,这部分的说明,Calcite官方并没有给出很好的文档指引。

toModificationRel会生成TableModify,在SqlToRelConverter中被调用,用于获取相关的上下文并保存到RelNode中,Interpreter调用TableModify最终会转换为EnumerableTableModify,并且在转换成执行的binary code时,调用getModifiableCollection()

TableModify是RelNode的子类,用于把DML操作转换为Enumerable操作,并实现了TableModify转换到Expression的逻辑。

继承于TableModify的EnumerableTableModify和JdbcTableModify,都实现了implement接口,但是两者的含义一样,实现并不一样。EnumerableTableModify扩展于EnumerableRel,但JdbcTableModify扩展于JdbcRel。

Calcite对语法树的解析,支持通过实现RelImplementor接口,来获得不同的implementor的能力,在系统中,现在有两个实现,分别是JavaRelImplementor和EnumerableRelImplementor,

Calcite DML的性能及优化思路

在现代计算机的单核CPU上,Calcite解析转换一条DML操作的耗时为毫秒级,对于需要复杂计算的select语句,转换的耗时是很小的。对于KtSQL的分布式架构来说,Calcite的SQL解析处理并不会成为瓶颈,反而是Enumerable的计算能力受限于CPU和内存。

单机的数据库系统,受限于单机的计算机架构,只能满足百万级数据的快速处理。JVM受限于其实现机制,千万级的数据处理就需要消耗上百G的内存,如果要把计算能力提升到十亿级别,则对计算能力的优化提出了极大的挑战。数据量越小,主键和二次索引对操作的影响越大。随着数据量的上升,则基于二次索引和范围处理的优化显得越发重要。Spark tungsetn、Kylin的RoaringBitmapIndex、SnappyData的基数引擎,都是计算优化的极好案例。Enumerable的优化有多种思路,比如支持网络堆外内存读取、堆外内存计算、异构计算加速,都可以有效提升计算的能力。这是将来可以优化的方向,而且其优化的思路与最终需适应的场景息息相关。

附录