随着大数据处理系统诸如流处理Flink,Storm,文本搜索Elastic,批处理Spark,OLAP系统Druid等的发展和流行,组织投资于根据其特定需求量身定制的数据处理系统,出现了两个问题:
- 开发人员开发此类系统遇到一些通用的问题,例如查询优化,查询语言的支持,SQL的扩展等等,不同的组织开发类似的功能是一种浪费
- 使用这些专用系统的程序员通常必须将其中的几个集成在一起。一个组织可能依靠Elasticsearch,Apache Spark和Druid。我们需要构建能够支持跨异构数据源的优化查询的系统
Apache Calcite就是为了解决这些问题。它是一个完整的查询处理系统,可提供除了数据存储和管理除外任何数据库管理系统所需的许多常用功能(查询执行,优化和查询语言)。
什么是Apache Calcite?
Apache Calcite是一个开放源代码动态数据管理框架,该框架已由Apache软件基金会许可,并使用Java编程语言编写。 Apache Calcite由许多组件组成,这些组件包括一个通用的数据库管理系统,但并不具有诸如存储数据和对该数据进行处理(由某些专用引擎完成)之类的关键功能。 由于Apache Calcite体系结构不支持数据的存储和处理,因此Apache Calcite的这一功能有助于在具有多个数据存储并使用多个数据处理引擎的应用程序之间进行中介。一些采用Calcite的处理引擎包含Apache Hive,Drill,Storm和Flink。 Apache Calcite包含许多组件,例如SQL解析器,查询优化器,SQL生成器,SQL验证器以及用于在关系代数中构建表达式的API。
嵌入Calcite的系统列表
适配Calcite的系统列表
Apache Calcite的优势
尽管Apache Calcite 架构不支持数据的存储和处理,但是它具有一些实用的功能,使它受益匪浅,如下:
- 开源友好性– Apache Calcite是由Apache Software Foundation维护的开源框架。由于它是用Java编写的,因此可以轻松地用许多数据处理引擎操作,这些引擎也用Java编写或在基于JVM的环境中运行,尤其是在谈论Hadoop生态系统时。
- 多种数据模型–由于Calcite支持查询优化和查询执行,但不支持数据的存储和处理,因此非常适合在具有多个数据存储位置并使用多个数据处理引擎的应用程序之间进行中介。
- 跨系统支持–在这里,跨系统支持意味着Apache Calcite框架能够跨多个查询处理系统或引擎以及数据库后端(如Apache Spark,Hive,Flink,Drill等)运行和优化查询。
- 支持SQL及其扩展–由于许多系统不提供自己的查询语言,因此更喜欢现有的查询语言,例如SQL。因此,对于那些系统,Calcite提供了对SQL方言和扩展的支持。
- 可靠性–Calcite非常可靠,因为它包含一个广泛的测试套件,该套件可以验证系统的所有组件,还包括查询优化器规则以及与后端数据源的集成。
Apache Calcite架构
Apache Calcite体系结构中包含各种组件–
SQL查询解析器和验证器–将SQL查询转换为关系运算符树。由于它不支持数据存储,因此它提供了一种定义表模式并借助适配器查看外部存储引擎的方法。
- 简单计划查询
- 优化查询
尽管Calcite为需要它的系统提供了优化的SQL支持,但它也为已经拥有自己的语言解析和解释的系统提供了优化支持。
某些系统支持SQL查询,但没有或仅有有限的查询优化。例如,Hive和Spark 最初提供对SQL语言的支持,但它们不包括优化器。对于这种情况,一旦查询得到优化,Calcite可以将关系表达式转换回SQL。此功能使Calcite可以作为具有SQL接口但没有优化程序的任何数据管理系统之上的独立系统工作。
Calcite体系结构不仅针对优化SQL查询而定制。数据处理系统通常选择使用自己的解析器作为自己的查询语言。Calcite也可以帮助优化这些查询。实际上,Calcite还允许通过直接实例化关系运算符来轻松构造运算符树。可以使用内置的关系表达式构建界面。
Apache Calcite如何工作?
在传递任何查询之前,我们必须告诉查询计划者当前有哪些模型和表。因此,第一步是解析模型并生成一个模式对象,为此目的,使用了Calcite的ModelHandler,并且为了编写模型处理程序,我们必须编写Calcite连接接口的虚拟实现并将其传递给模型处理程序。调用ModelHandler之后,从该虚拟连接对象中,我们将获得schemaObject并将其与查询计划程序一起使用。
我们假定有如下的查询,需要从Splunk和Mysql中进行Join来查询数据
SELECT p.“product_name”, COUNT(*) AS c
FROM “splunk”.”splunk” AS s
JOIN “mysql”.”products” AS p
ON s.”product_id” = p.”product_id”
WHERE s.“action” = 'purchase'
GROUP BY p.”product_name”
ORDER BY c DESC
SQL的执行流程如下图
优化前
经过优化,Calcite把查询条件前置,查询的性能得到优化,而查询的结果是一致的。
优化后
上图是Calcit的数据源适配器的关系图。
适配器是一种架构模式,定义了Calcite如何合并各种数据源以进行常规访问。。本质上,适配器由模型Model,模式Schema和模式工厂schema factory组成。
- 模型model是被访问的数据源的物理属性。
- 模式schema是在模型中找到的数据(格式和布局)的定义。数据本身可以通过表进行物理访问。Calcite与适配器中定义地表Table进行接口,以在执行查询时读取数据。适配器可以定义一组规则,这些规则已添加到计划器中。例如,它通常包含将各种类型的逻辑关系表达式转换为适配器约定的对应关系表达式的规则。
- 模式工厂schema factory组件从模型获取元数据信息并生成模式
Planner Rule。Calcite包括一组计划程序规则以进行转换表达树。特别是,规则与树并执行保留该表达式语义的转换。Calcite包括数百个优化规则。但是,它对于数据处理系统来说相当普遍。依靠Calcite进行优化可以包括自己的规则,允许特定的重写。
下图是一个规则的例子:
它的Java实现的代码如下:
class FilterIntoJoinRule extends RelOptRule {
public FilterIntoJoinRule() {
super(
operand(Filter.class,
operand(Join.class, any())));
}
public void onMatch(RelOptRuleCall call) {
Filter filter = call.rel(0);
Join join = call.rel(1);
Filter newFilter = ...;
Join newJoin = ...;
call.transformTo(newJoin);
}
}
下图是Calcite中的API和SPI
查询优化器面临的挑战
查询优化和进行有效查询时面临的一些挑战是联接Join优化和使用表时的表大小。联接Join是利用两个表中共同的字段值来合并两个表中的行。因此,用于联接表的顺序将影响数据集的大小,并将影响性能。因此,查询优化器的主要优点之一是能够找到最佳的联接顺序。在查询的执行计划中未定义连接顺序。
查询优化器考虑两种不同类型的树,它们是:
左深节点
左深树的所有内部节点都至少有一个叶节点作为子节点。虽然实现起来很简单,但是这种技术并没有给出有效的结果。在这种技术中,我们首先将两个表联接起来,然后将结果表联接起来与第三个表联接,该表的结果与另一个表联接,依此类推,直到执行联接操作为止。
浓密的树节点
在这种情况下,一个联接基于两个联接的结果进行运算。这些树还包含子树,并且它们与其他子树的连接比左深度树的连接效率更高。在这种技术中,如果存在要联接的四个表,那么我们首先在两个表上应用联接操作,然后对另外两个表执行联接,然后获得两个联接操作的结果表,最后联接为了执行所有表而执行操作。
总结
Caclite提供一个可插拔的架构支持异构数据源的查询,它的动态和灵活的查询优化是最被广泛使用的功能。