写点什么

Apache Calcite:异质数据源优化查询框架

用户头像
余生
关注
发布于: 2021 年 06 月 07 日
Apache Calcite:异质数据源优化查询框架

该篇文章是研究 Apache Calcite 时看到的一篇论文:https://dl.acm.org/doi/abs/10.1145/3183713.3190662,其对 Apache Calcite 的方方面面作了整体的介绍。为细细研究,故将其翻译成中文,以供后续学习。

摘要

Apache Calcite 是一个基础的软件框架,能够为许多流行的开源数据处理系统提供查询处理、优化和查询语言的能力支持,例如:Apache Hive,Apache Storm,Apache Flink,Druid 以及 MapD 等。Calcite 包含查询优化器、查询处理器以及适配器等组件,模块化的查询优化器可支持扩展,其内置上百种优化规则;查询处理器,能够支持各种各样的查询语言;而适配器的设计,可以支持各种异质数据模型或存储的适配,诸如:关系型、半结构化、流式以及地理空间数据等。这种灵活(flexible)、可嵌入(embeddable)且可扩展的架构设计使得 Apache Calcite 在大数据处理框架上,成为一个很好的选择。


关键词

Apache Calcite,语义关系(Relational Semantics),数据管理(Data Management),查询代数(Query Algebra),模块化查询优化(Modular Query Optimization),存储适配器(Storage Adapters)

1 引言

常规关系数据引擎依然主导了数据处理领域。早在 2005 年,Stonebraker and Çetintemel[49]已经预言,我们将会见到一系列专用引擎,比如,列式存储、流处理引擎、文本处理引擎等。他们认为专用引擎能够提供更好的性能且能终结“one size fits all”范式。今天,他们的愿景似乎比以往任何时候都更加重要。事实上,很多专用的开源数据系统已经很流行,像 Apache Storm 和 Apache Flink(流处理),Elasticsearch(文本检索),Apache Spark, Druid 等等。


基于专用需求量身定制的数据处理系统,存在以下两方面的问题:

  • 专用系统的开发者遇到一些问题,例如查询优化(query optimization)、基于 SQL 的查询语言及相关扩展能力支持,以及由 LINQ 启发的语言集成查询框架。在没有统一的框架情况下,会导致工程师各自独立开发相类似的查询优化和语言支持,导致重复工作,浪费人力。

  • 开发者经常需要将多个专用引擎整合起来。一个系统可能依赖 Elasticsearch、Apache Spark、Druid,这样的情况就得需要需要支持跨异质数据源查询能力。


Apache Calcite 开发的初衷就是来解决这些问题。Calcite 是一个完整的查询处理系统,能够提供任何数据管理系统支持的多种通用功能,包括查询执行、优化以及查询语言。但是 Calcite 有两个方面没有实现,一是数据存储,二是数据管理,这两方面的功能留给了专用引擎来实现。Calcite 可以快速地被 Hive,Drill,Strom 和很多其他的数据处理引擎集成,为其提供高级的查询优化以及查询语言支持。举个例子,Hive 是一个构建在 Apahce Hadoop 上的流行的数据仓库项目。随着 Hive 方向从批处理转向基于 SQL 的交互式查询,其最需要解决的就是强大的查询优化器。因此,Hive 采用 Apahce Calcite 作为其优化器且集成度越来越高。许多其他的项目和产品也跟 Hive 类似,集成了 Apache Calcite 的能力,包括 Flink,MapD 等等。


此外,通过向多种系统暴露标准接口,Apache Calcite 能够提供跨平台优化支持。为了提高效率,优化器需要提供全局来处理的能力,比如在不同的系统上做物化视图选择的判断。


构建一个通用的框架有很多的挑战,特别是该框架要有足够的灵活性和可扩展性,且能够适配集成的系统。我们相信以下特性能够有助于 Apache Calcite 在开源社区和行业中的广泛采用:

  • 开源友好性。在过去的 10 年中,大部分主要的数据处理平台要么是开源要么就是大部分基于开源研制的。Apache Calcite 是一个 Apache 的开源框架。此外,Apache Calcite 是由 Java 语言开发,使其能够与其他由 Java 或 Scala 实现的开源数据处理系统更好地集成,尤其是 Hadoop 生态系统。

  • 多数据模型。Apache Calcite 提供流式和常规数据处理范式的查询优化和查询语言的能力支持。Calcite 把流当成是记录或事件的时间顺序集合,这些数据和传统的数据处理系统不一样,其并不持久化到磁盘。

  • 灵活的查询优化器。从规则到成本模型,每一个优化器的组件均是可插拔且可扩展的,另外,Calcite 支持多个计划引擎。所以,优化可以被拆分到不同的优化阶段,这样可以找到更适合的优化规则来进行优化。

  • 跨系统支持。Calcite 框架可以跨多个查询处理系统和数据库进行查询和优化。

  • 可靠性。Calcite 具有很好的可靠性,在被采用的这些年,其经受住了各类平台详尽的测试。同样地,Calcite 本身包含详尽的测试套件,用以测试检验查询优化器的规则以及数据源集成的组件。

  • 支持 SQL 及扩展。很多系统不提供自己的查询语言,而是更愿意依赖于现有的查询语言,例如 SQL。对于这些情况,Calcite 提供了支持 ANSI 标准的 SQL 以及各种 SQL 方言和扩展的支持,例如基于流或者嵌套数据的查询。另外,Calcite 提供了符合 Java API(JDBC)的驱动程序。

2 相关工作

在 hadoop 生态大数据分析领域,Calcite 目前是被广泛采用的优化器,其实 Calcite 背后的实现并不新颖。例如,查询优化器的思路来自于 Volcano 和 Cascades 框架,并结合其他被广泛使用的优化技术,如物化视图重写。有很多其他的系统跟 Calcite 类似。


Orac 是一款模块化的查询优化器,其目前被应用于 Greenplum 和 HAWQ 数据管理产品中。Orac 引入一个数据交换语言(Data eXchange Language)框架,其实现了优化器与执行引擎两者的分离。除此之外,Orac 还对其生成的查询计划提供一个校验正确性和性能的能力。与之相比,Calcite 可以被当作一个独立的查询执行引擎,能够适配多个存储和处理后端,包括可插拔的计划器和优化器。


Spark SQL 扩展 Apache Spark 以支持 SQL 化查询执行。该查询也可以像 Calcite 一样,对多个数据源执行查询。然而,尽管 Spark SQL 中的 Catalyst 优化器也尝试最小化查询执行成本,但是其缺乏类似于 Calcite 中的动态编程方法,且容易陷入局部极小值的风险。


Algebricks 是一种查询编译器体系架构,为大数据查询提供了数据模型无关的代数层和编译器框架。高级语言被编译成 Algebricks 逻辑代数。然后,Algebricks 生成针对 Hyracks 并行处理后端的优化作业。虽然 Calcite 与 Algebricks 一样,采用了相同的模块化方法,但是 Calcite 在此基础上还提供了成本优化支持。在 Calcite 当前版本中,查询优化器架构基于 Volcano 采用了动态编程计划,并同样具备 Orca 多阶段优化的扩展能力。尽管 Algebricks 能够支持多个处理后端(例如:Apache Tez、Spark),但是多年来,Calcite 已经经受住各种后端的良好测试考验。


Garlic 是一个异构数据管理系统,其基于统一的对象模型表示来自多个系统的数据。但是 Garlic 无法根据不同的系统来提供查询优化支持,而是依赖系统本身来优化查询。


FORWARD 是一个联邦查询处理器,该查询处理器实现了一种叫 SQL++的查询语言,该查询语言为 SQL 的超集。SQL++将 JSON 和关系数据模型整合在一起,形成半结构化数据模型,而 Calcite 则是在查询计划阶段,用关系数据模型来表示半结构化数据模型。FORWARD 将基于 SQL++的联邦查询拆解为子查询,并根据查询计划在具体的数据库执行,而数据的合并则在 FORWARD 引擎中。


另外一个联邦数据存储和处理系统是 BigDAWG,该系统抽象了各类数据模型,包括关系、时间序列以及流。在 BigDAWG 中,具体抽象单元称之为信息岛(island of information)。每一个信息岛包含查询语言、数据模型以及一个或多个存储系统的连接。只有在单个信息岛内,跨存储系统的查询才能支持。而 Calcite 提供了统一的关系抽象,该抽象支持跨不同数据模型的后端进行查询。

Myria 是一个通用的大数据分析引擎,其支持 Python 语言。该引擎为各类后端引擎生成查询计划,比如 Spark 和 PostgreSQL。

3 架构

Apache Calcite 包含了许多典型数据库管理系统具备的组件,但是也放弃了一些关键的组件,例如:数据存储、数据处理算法和元数据存储。放弃这些其实是 Calcite 有意为之,这使得 Calcite 成为一种更好的选择,用于衔接多数据存储以及多数据处理引擎,同时也为构建定制化的数据处理系统提供坚实的基础。


图1 Apache Calcite 架构和交互


图 1 展示了 Apache Calcite 架构的主要组件。Calcite 采用关系操作树作为其内部表示。内部优化引擎主要包括三个组件:规则,元数据和计划引擎,我们将在第 6 部分详细讨论这些组件。图中,虚线表示与外部框架的交互。Caclcite 提供了多种方式与外部框架交互。


首先,Calcite 包含一个查询处理器和校验器能够将 SQL 查询语句转换成关系操作树。Calcite 不支持存储功能,而是通过适配器,提供了在外部存储引擎中定义数据表 schemas 和视图的标准。因此,可以在这些引擎上使用 Calcite。


其次,Calcite 不仅为需要数据库语言支持的系统提供了优化的 SQL 支持,而且对那些本身具体语言解析与解释的系统,Calcite 也能提供优化支持:

  • 有些系统支持 SQL 查询,但是没有或者只是有限的 SQL 查询优化。例如,Hive 和 Spark 最初均提供了对 SQL 查询的支持,但是它们并没有优化器的能力。对于这些情况,一旦查询被优化后,Calcite 可以将关系表达式转回 SQL。这种功能使得 Calcite 能够作为独立的系统模块,构建在那些有 SQL 接口,但无优化器的数据管理系统上。

  • Calcite 架构不仅仅面向 SQL 查询优化。通常数据处理系统会选择自己的查询语言和相应的解析器。对于这种情况,Calcite 也能起作用。事实上,Caclite 利用内置的关系表达式构建接口,也能通过直接初始化关系操作符类,来构建操作树。例如,假设我们使用表达式构建器来表示以下 Apache Pig 脚本。

emp = LOAD 'employee_data' AS (deptno, sal);emp_by_dept = GROUP emp by (deptno);emp_agg = FOREACH emp_by_dept GENERATE GROUP as deptno, COUNT(emp.sal) AS c, SUM(emp.sal) as s;dump emp_agg;
复制代码

使用 Calcite 构建器来表示,具体如下:

final RelNode node = builder.scan("employee_data")    .aggregate(builder.groupKey("deptno"),               builder.count(false, "c"),               builder.sum(false, "s", builder.field("sal")))    .build();
复制代码

该接口展示了构建关系表达式的方式。当优化阶段结束后,应用程序可以检索优化后的关系表达式,然后将其映射回系统查询单元。

4 查询代数    

操作符(Operators)。关系代数是 Calcite 的核心部分。除了最常见的诸如过滤(filter)、映射(project)、关联(join)等,Calcite 提供了额外的操作符来满足不同的需求,比如能够简单表示复杂操作或者更有效识别优化时机。


举例来说,对于 OLAP,决策支持和流应用来说,使用窗口函数来表达复杂分析功能已经变得很常见(例如,按时间段、数值或行数进行窗口滑动)。为此,Calcite 引入了窗口(window)操作符来封装窗口的定义(上下限,分区等)以及窗口上执行的聚合函数。


特质(Traits)。Calcite 没有使用不同的实体来表示逻辑和物理操作符。相反,其使用特质来描述与操作相关的物理属性。这些特质能够帮助优化器来评估不同替代计划的执行成本。修改特质的值并不会改变需要评估的逻辑表达式,即,指定操作符生成的行仍然是一样的。


图 2 查询优化过程


在优化过程中,Calcite 会尝试在一些关系表达式上强制执行指定的特质,例如,某些列的排序顺序。关系操作符实现了一个转换接口,用来标识如何将将表达式的特质从一个值转换为另一个值。


Calcite 包含了常用的一些特质,这些特质描述了关系表达式生成的数据物理属性,例如排序(ordering),分组(grouping)和分区(partitioning),和 SCOPE 优化器类似,Calcite 优化器可以推理这些属性,并且利用这些推理来找到不必要的操作计划。例如,若排序操作符的输入已经正确排序(行排序与后台系统的行排序相同),那么排序操作就可以移除。


除了这些特性,Caclite 还包括一个重要的特质,即调用约定(calling convention)。本质上,特质表示表达式执行的数据处理系统。将调用约定当作特质,可以使 Calcite 能够保持透明的优化查询,对于跨引擎执行而言,该约定被看作是其他物理属性。


我们拿具体的示例来说明下,如图 2 所示,做 Products 和 Orders 的跨存储关联, Products 保存在 MySQL 中,而 Orders 表则保存在 Splunk 中。首先, 在 Splunk 中扫描 Orders表,在 Mysql 中扫描 Products表,而关联操作在逻辑上没有选择具体的执行引擎。而且,图 2 上的 SQL 查询包含过滤操作,该操作通过适配器具体的规则,详见第 5 节,将过滤操作下推至 splunk 中执行。对于关联操作,一种可行的解决方案是将关联操作转成 Spark 实现,且将 MySQL 和 Splunk 的输入也转换至 Spark 实现。还一种更有效的实现方式,Splunk 利用 ODBC 方式执行 MySQL 表的查询,如此就可以实现整个操作在 Splunk 引擎中执行。

5 适配器

Calcite 中的适配器一种架构模式,其定义了对各种数据源如何进行访问。图 3 描绘了适配器的组件,从图上我们可以看出,适配器主要由模型(model),Schema 和 SchemaFactory 组成。模型定义了访问数据源所需要属性的结构,Schema 则是模型中数据的定义,包括数据格式和布局。数据本身则是通过表进行访问,Calcite 借助适配器中关于表的定义来读取数据,查询的执行都会依赖表的定义。另外,适配器可以向计划器中添加自己的一组规则,例如如何从将各种类型的逻辑关系表达式向具体适配器约定的关系表达式转换。SchemaFactory 则从模型中获取元数据信息并生成 schema。


图3 Calcite数据源适配器设计


如第 4 节讨论的那样,Calcite 使用调用约定特质来识别关系操作符并与指定数据库对应。这些物理操作符为每个适配器中潜在的表提供访问路径。当 SQL 被解析完并转换成关系代数表达式时,将为每个表创建一个基于表的 scan 操作符。scan 操作符是适配器必须实现的最小接口。若某个适配器实现了表 scan 操作符,Calcite 优化器就能够使用排序(sorting)、过滤(filtering)和关联(joins)操作符来对这些表执行任意 SQL 查询。


表 scan 操作符包含了适配器所需要必要信息,这些信息可以用来扫描适配器的数据库。为了扩展适配器的功能,Calcite 以枚举的方式定义了调用约定。调用约定的关系操作符通过迭代器接口对元组进行操作。这种约定可以让适配器定制自己的操作符,例如,枚举关联(EnumverableJoin)操作符通过收集其子节点的行数据,然后在指定的属性上进行关联。


对于具体表中小数据量级数据的查询,如果 Calcite 需要枚举所有的元组,这种效率较低。对此种情况,可以使用基于规则的优化器来对指定的适配规则进行优化,例如,某个查询涉及到对表进行过滤和排序。适配器能够实现将逻辑过滤转换成适配器的调用约定,使其能够在存储侧进行过滤,该规则将逻辑过滤转换成另一个过滤实例。这种具体较低成本关联的过滤节点可以让 Calcite 根据适配器来优化查询。


适配器的抽象非常强大,其不仅可以优化具体存储的查询,同时也可以跨多个存储进行优化。通过将所有可能的逻辑下推到具体存储,然后对结果数据进行关联和聚合,这使得 Calcite 能够很好支持跨存储表数据的查询。实现自定义的适配器跟实现一个表 scan 操作符一样简单。关系代数中使用的表达式都可以使用优化器规则将其下推到适配器中。

6 查询处理与优化

查询优化器是 Calcite 框架中的主要组件,通过重复应用计划规则于关系表达式上来达到优化查询的目的。Calcite 成本模型指导整个流程,而计划引擎尝试生成与原表达式相同语义且成本更低的可替代表达式。

优化器中每个组件都是可扩展的,用户可以添加关系操作、规则、成本模型和统计。


计划规则(Planner rules)。Calcite 包含一系列计划规则用于转换表达式树。特别地,规则匹配表达式树的指定模式并执行表达式的转换,该转换过程会保留原有表达式的语义。Calcite 内置了数百个优化规则,对于依赖 Calcite 进行优化的数据处理系统来说,也可以定制或重写已有的规则。例如,Calcite 为 Apache Cassandra 提供了一个适配器,Cassandra 是一个宽列式存储,其按表中列的子集对数据进行分区,然后在每个分区中根据其余列的子集进行排序。如第 5 节中所述,对于适配器来说,尽可能将查询下推至具体存储执行能够提高效率。将排序规则推入 Cassandra 必须满足两个条件:

1、该表已经被过滤到单个分区(因为在单分区内才能支持排序),

2、Cassandra 中分区排序必须具备公共的前缀。

这就要求需要重写 CassandraFilter 来覆盖 LogicalFilter 以保证分区过滤被下推至 Cassandra 库中。将规则从 LogcialSort 转换至 CassandraSort 是简单的,但是在复杂场景下,保证下推操作符规则的灵活性很难。考虑如下有复杂影响规则的查询语句:

SELECT products.name, COUNT(*)FROM sales JOIN products USING (productId)WHERE sales.discount IS NOT NULLGROUP BY products.nameORDER BY COUNT(*) DESC;
复制代码

上面查询对应的关系代数表达式如图 4 中的左图,由于 WHERE 子句只包含 sales 表的过滤,我们可以将过滤下推,如此就变成图 4 中右图所显示的关系代数表达式,这种优化可以显著减少查询的执行时间,因为我们无需对谓词匹配的行执行关联操作。此外,如果 sales 表和 pruducts 表保存在各自的存储中,将 filter 调至 join 之前可以使适配器 filter 下推至具体的存储。Calcite 通过 FilterIntoJoinRule 规则实现了这样的优化,该规则检测 Filter 节点的父节点是否为 JOIN,如果是则执行前文说到的优化。这种的优化方式使得 Calcite 的优化方法非常灵活。


图4 FilterIntoJoinRule 应用


元数据(Metadata providers)。元数据是 Calcite 优化器的重要组成部分,其有两个主要目的:1、指导优化器如何降低整体查询开销成本;2、当应用优化规则时,元数据为这些规则提供相应的信息。


元数据主要职责是为优化器提供相应的信息。特别是,Caclite 中默认元数据提供了函数实现,包括:操作树中执行子表达式的成本开销、执行行数、执行表达式结果数据的大小以及执行的最大并行度。反过来,其还可以提供有关计划结构的信息,例如,存在具体树结点下的过滤条件。


Calcite 为数据处理系统提供了注入自定义元数据的接口。这些系统可以重写 Calcite 原有的元数据提供者(providers),或是提供自定义的新元数据函数以用于优化阶段。然而,对于大多数人而言,提供输入数据统计信息就已经足够,包括表的行数、表的大小以及给定列的值是否唯一。其余工作交给 Calcite 来完成即可。


由于元数据提供是可插拔的,所以其编译和执行是使用 janino 来完成,janino 是 Java 的一个轻量级编译器。该实现包括可显著提高性能的元数据结果缓存。例如当我们需要计算多种类型的元数据(指定关联的基数、平均行大小以及可选择性)时,,且这些计算需要依赖其输入。


计划引擎(Planner engines)。计划引擎的主要功能是不断触发优化规则,直至达到最优结果。当前,Calcite 默认提供了两种计划引擎,用户也可以按照 Calcite 规范,自定义计划引擎。


第一种是基于成本的计划引擎,该计划引擎以降低整个查询的执行成本为目标。该计划引擎使用的动态编程算法,类似于 Volcanno,可以创建和跟踪不同可选计划,这些计划由规则触发而生成。首先,需要向计划引擎注册表达式以及基于表达式的属性和输入摘要。当在表达式 e1上触发某规则,那么该规则会生成新的表达式 e2 ,然后计划引擎会将 e2 添加至 e1 所属的等价表达式集合 Sa中。此外,计划引擎会为该新的表达式生成一个摘要,该摘要将会与之前注册的其他表达式摘要进行比较。如果发现与一个 e3 的表达式摘要相似,而这个表达式 e3属于 Sb集合,那么计划引擎会合并 SaSb两个集合。该过程直至达可配置的固定点才会停止。需要指出的是,计划引擎会搜索所有的规则空间以保证所有的规则都应用至整个表达式,或者采用启发式的方法来停止优化,当再进行计划迭代时,所花的成本不超过指定阈值。借助元数据提供的信息,成本函数可以判定选择使用哪个优化规则。Calciter 提供的默认成本函数实现,将给定表达式所需要 CPU、IO 以及内存资源也考虑进来。


第二种计划引擎是一个穷举计划引擎,该计划引擎穷举计划规则直至生成一个不再发生变化的表达式。这种计划引擎有助于快速执行规则,不需要考虑每个表达式的执行成本。


用户可以根据具体的需求场景来选择采用哪种计划引擎,当需求发生变化时,也可以在两个计划引擎之间进行切换。或者,用户可以选择生成多个阶段优化逻辑,并将之应用到优化过程中的衔接阶段。重要的是,该两种计划引擎通过检索不同查询计划,能够帮助 Calcite 用户减少整体的优化时间。


物化视图(Materialized views)。数据仓库中加速查询速度的最强大技术之一就是相关摘要的预计算或物化视图。Calcite 的适配器以及依赖 Calcite 的项目在物化视图上也有相应的解决方案。例如,Cassandra 允许用户基于已有表自定义物化视图,且物化视图由系统自动来维护。


类似 Cassandra 的引擎,将其视图暴露给 Calcite。Calcite 的优化器可以使用这些视图来重写查询语句,而不是查询原始表。且 Calcite 提供了两种物化视图重写算法的实现。


第一种方法是基于视图替换算法。该算法的目的是利用了物化视图表达式来替代关系代数树中的等价部分。算法过程如下:1、将物化视图的 scan 操作符以及物化视图定义计划注册到计划引擎中;2、尽量统一计划中触发的转换规则。视图不需要与被替换的查询表达式完成匹配,因为该替换算法可以实现部分重写,其中包括额外的操作符来计算所需的表达式,例如:残留谓词条件过滤器。


另外一种算法是基于 lattices。一旦数据源被声明为 lattice,Calcite 会将每一个物化表示为一个 tile,优化器能够利用tile来优化查询。一方面,在星型模式组织的数据源上,该重写算法匹配表达式非常有效,星型模式组织的数据源在 OLAP 中则很常见。另一方面,因其对底层架构有限制,导致其比视图替换的限制性更高。

7 Calcite 扩展

如前文所提到的,Calcite 不仅仅适用于 SQL 处理。在其他数据源上,Calcite 也提供了 SQL 查询能力支持,例如半结构化、流式以及地理空间数据。Calcite 内置了操作符来适配这些查询。除了可扩展的 SQL 外,Calcite 还包括语言集成的查询语言。我们将在下文描述这些扩展并提供相应的示例。

7.1 半结构化数据

Calcite 支持几种复杂的列数据类型,这些类型可以将关系型和半结构化数据存储在表中。具体来说,列类型包括 ARRAY, MAP, 或者 MULTISET。此外,这些复杂类型还支持嵌套,比 MAP 数据类型,其值的类型为 ARRAY。我们可以使用[]操作符来提取 ARRAY 和 MAP 以及其内嵌套的数据,且这些复杂类型中值的具体类型不需要预先定义。我们举个具体的示例,Calcite 提供了 MongoDB 的适配器,MongoDB 是一个文件存储引擎,其数据文件格式类似于 JSON 文件。为了能让 Calcite 查询到 MongoDB 的数据,我们需要为每一个文档集合创建一张表,且该表只有一列,列名为_MAP:文档标识符到数据的映射。表示邮政编码的文档集合可能包含城市名称、纬度和经度的列,这种将提取出来的数据作为关系型数据表非常有用,在 Calcite 中,当抽取出期望的值以及将之转换成合适的类型后,就可以转换成视图了。

SELECT CAST(_MAP['city'] AS varchar(20)) AS city,    CAST(_MAP['loc'][0] AS float) AS longitude,  CAST(_MAP['loc'][1] AS float) AS latitudeFROM mongo_raw.zips;
复制代码

以这种方式在半结构化数据上创建视图,可以让我们很容易无缝操作半结构化数据与结构化数据。

7.2 流式数据

Calcite 提供了非常好的流式查询支持,在标准 SQL 的基础上,Calcite 定制了一组流式扩展,包括窗口扩展(windowing extensions),关联查询。这些扩展的思路来源于连续查询语言(Continuous Query Language)的启发,并试图跟标准 SQL 有效集成起来。 STREAM明确告诉诉用户对新增的记录感兴趣而不是已有的。

SELECT STREAM rowtime, productId, unitsFROM ordersWHERE units > 25;
复制代码

如没有指定关键字 STREAM ,上文查询就变成常规的 SQL 查询,表示系统应该处理从流中已接收到的数据,而不是新接收的数据。


由于流本质上是无界的,窗口操作主要用来支持聚合和关联操作符。Calcite 流扩展使用 SQL 分析函数来表达滑动和级联窗口聚合,具体示例如下:

SELECT STREAM rowtime,    productId,  units,  SUM(units) OVER (ORDER BY rowtime    PARTITION BY productId    RANGE INTERVAL '1' HOUR PRECEDING) unitsLastHour  FROM Orders;
复制代码

滚动(Tumbling),跳跃(hopping)和会话窗口(session)功能可以使用 TUMBLE , HOPPING, SESSION函数和一些工具函数( TUMBLE_END 和 HOP_END )来支持,这几个关键可以在 GROUP BY 或 SELECT 中来使用。

SELECT STREAM    TUMBLE_END(rowtime, INTERVAL '1' HOUR) AS rowtime,  productId,  COUNT(*) AS c,  SUM(units) AS unitsFROM ordersGROUP BY TUMBLE(rowtime, INTERVAL '1' HOUR), productId;
复制代码

如果进行滑动以及级联窗口的流式查询,需要在 GROUP BY 子句或 ORDER BY 子句中写单调或准单调表达式。可以在关联 JOIN 子句中使用隐式(时间)窗口表达式来支持复杂的流与流之间的关联查询。

SELECT STREAM o.rowtime, o.productId, o.orderId, s.rowtime, AS shipTimeFROM orders AS oJOIN shipments AS sON o.orderId = s.orderId AND s.rowtime BETWEEN o.rowtime AND o.rowtime + INTERVAL '1' HOUR
复制代码

在隐式窗口的情况下,Calcite 的查询计划器会验证表达式是否是单调的。

7.3 地理空间数据

利用 Calcite 关系代数,Calcite 初步提供地理空间数据查询的能力支持。在核心实现中,Calcite 添加了 GEOMETRY 关键字,该关键字实现了对不同地理空间对象的封装,包括点(points)、曲线(curves)以及多边型(polygons)。后续 Calcite 将会完全符合 OpenGIS Simple Feature Access 规范,该规范定义地理空间数据的 SQL 接口标准。下面示例是查询阿姆斯特丹市的国家:

SELECT name FROM (    SELECT name,    ST_GeomFromText('POLYGON((4.82 52.43, 4.97 52.43, 4.97 52.33, 4.82 52.33, 4.82 52.43))') AS "Amsterdam",    ST_GeomFromText(boundary) AS "Country"    FROM country)WHERE ST_Contains("Country", "Amsterdam");
复制代码

7.4 集成 Java 语言查询

Calcite 能够支持多种数据源的查询,而不仅仅是关系型数据库,同时,也不仅仅是只支持 SQL 语言查询。尽管 SQL 仍然是主流的数据库查询语言,但是很多开发者也比较喜欢像 LINQ 这样,支持语言集成的语言。与嵌入进 Java 或 C++代码中的 SQL 不同,语言集成的查询语言允许开发者使用单一语言编码所有代码。Calcite 为 Java(简单 LINQ4J)提供了语言集成查询,其严格遵循 Microsoft 的 LINKQ 为.NET 语言制定的约定。

8 业界及学术界使用

Calcite 在各类开源项目中被广泛使用。由于 Calcite 提供了良好的集成灵活性,所以这些项目一般有两种方式集成 Calcite,一种是直接将 Calcite 嵌入项目中,即将其当作库使用;一种是实现 Calcite 定义的适配器接口,使用 Calcite 来作联邦查询。此外,我们看到在学术界也在使用 Calcite 作为数据管理项目解决方案。在下文中,我们将描述不同的系统如何使用 Calcite。

8.1 嵌入式 Calcite

表 1 显示了使用 Calcite 作为核心库的软件列表,包括:1、暴露给用户的查询语言接口;2、是否使用 Calcite 的 JDBC 驱动;3、是否使用 Calcite 中的 SQL 解析和校验;4、是否使用 Calcite 的关系代数表示其数据上的操作;5、依赖 Calcite 执行的引擎,例如集成 Calcite 的本地引擎,Calcite 操作符,或任何其他项目。


表1 嵌入Calcite系统列表


Drill 是一个基于 Dremel 系统,灵活的数据处理引擎。其内部采用 JSON 无关的文档数据模型。类似于 SQL++, Drill 使用自己的 SQL 方言,同时为了支持半结构化数据,对 SQL 进行了扩展。


Hive 因作为 MapReduce 编程模型之上的 SQL 接口而流行起来,后来 Hive 朝着交互式的 SQL 查询引擎方向演进,并采用 Calcite 作为其规则及成本优化器。Hive 并不依赖 Calcite 的 JDBC 驱动程序,SQL 解析和校验,而是有自己的实现。Hive 的查询语句被转成 Calcite 操作符,优化之后再转成 Hive 的物理代数。Hive 操作符能够被多种引擎执行,最流行的当属 Apache Tez 和 Apache Spark 引擎。


Apache Solr 一款流行的全文分布式检索平台,其构建在 Apache Lucene 库基础上。Solr 向用户暴露了多种查询接口,包括类 REST 的 HTTP/XML 以及 JSON 接口。此外,Solr 集成 Calcite 并提供了 SQL 能力匹配。

Apache Phoenix 和 Apache Kylink 均是在 Apache HBase 上构建的,Apache HBase 是一款分布式 KV 存储模型。特别是,Phoenix 提供了一个 SQL 接口和编排层来查询 HBase。相反,Kylin 专注于 OLAP 的 SQL 查询,通过声明物化视图和 HBase 中的数据构建 cubes,因此基于这些 cubes,可以使用 Calcite 优化器重写输入查询。在 Kylin 中,查询计划使用 Calcite 本地操作符和 HBase 组合来执行。


最近,Calcite 在流处理系统方面变理流行起来。诸如,Apache Apex,Flink,Apache Samza 以及 Storm 均集成了 Calcite,使其组件能够向用户提供流式 SQL 接口。最后,其他的商业系统也有采用 Calcite,例如 MapD,Lingual 和 Qubole Quark。

8.2 Calcite 适配器

相较于当 Calcite 当作库来使用,其他的系统集成 Calcite 时,则采用实现 Calcite 提供适配器接口来读取其数据源。表 2 展示了 Calcite 中支持的适配器列表。实现这些适配器最关键是要实现 converter组件,该组件负责将推送给系统的代数表达式转换成该系统支持的查询语言。表 2 还显示了 Calcite 将代数表达式转换成的目标语言。


表2 Calcite适配器列表


JDBC 适配器支持多种 SQL 方言的生成,包括比较流行的 PostgreSQL 以及 MySQL。反过来,Cassandra 则有类 SQL 查询语言,称之为 CQL。而 Apache Pig 则在 Pig Latin 中生成自己的查询表达式。Apahce Spark 的适配器采用 Java RDD 接口。最后,Druid,ElasticSearch 以及 Splunk 则是通过 REST HTTP 接口来进行查询,由 Calcite 生成的查询表达式为 JSOM 或 XML 格式的。

8.3 研究中使用

在研究场景下,Calcite 可以作为精准医疗和临床分析场景下的一种选择。在这些场景中,需要对异质医疗数据进行逻辑整合和对齐,这样有助于基于患者更全面的病史和基因组谱来评估最佳治疗方案。数据主要存放在科学数据库中,主要涉及患者的电子病历,各类结构化、半结构化报告(肿瘤学、精神病学、实验室测试、放射学等)、成像、信号以及序列数据。在这些情况下,Calcite 的统一查询接口和灵活的适配器架构能够很好地提供支持。正在进行的研究旨在为数组和文本源引入新的适配器以及能够支持高效连接异构数据源。

9 将来工作

将来 Calcite 将专注于新功能的研发以及适配器架构的扩展:

  • 加强 Calcite 作为独立引擎所需要的设计。具体涉及 DDL,物化视图,索引以及约束。

  • 计划器的设计和灵活性持续改进。包括模块化,用户可定制计划器。

  • 整合新参数方法到 Calcite 优化器中。

  • 支持扩展的 SQL 命令,函数,工具,完全符合 OpenGIS 规范。

  • 非关系型数据源的适配器支持,例如科学计算方面的数组数据库。

  • 性能分析和检测的改进。

9.1 性能测试与评估

尽管 Calcite 包含性能测试模块,但却不评估查询执行。如果能够对集成 Calcite 的系统进行性能评测,那将非常有用。比如我们可以对比相类似框架使用 Calcite 的性能,但是很不幸,很难公平的进行比对。例如,像 Calcite 一样,Algebricks 优化了 Hive 的查询。Borkar 对比了带 Hyracks 的 Algebricks 调度程序与 Hive 0.12 版本进行了比较。他们的工作促进了 HIve 工程和架构的改进。以一种公平的方式来对比 Calcite 和 Algebricks,比如时间,但是并不太可行,因为需要确保每一部分都使用相同的执行引擎。Hive 应用要么依赖于 Apache Tez,要么信赖于 Apache Spark,而 Algebricks 则采用自己的框架,包括 Hyracks。


此外,如果要评估基于 Calcite 框架系统的性能,我们需要考虑两个用例。事实上,Calcite 可以作为单个系统的一部分,作为加速系统构建的工具,也可以作为公共层,用于多个独立系统的联邦查询。前者与数据处理系统的特性有关,且由于 Calcite 被广泛的使用,因此需要许多不同的测试基准。后者则受现有异构基准的可性用限制。BigDAWG 已经被用于集成 PostgreSQL 与 Vertica 的集成,并且在标准基准测试中,人们认为集成的系统优于将跨存储的表复制到一起以进行特定存储的查询。基于现实世界的经验,我们相信集成跨存储查询的能力是一个很值得期待的目标,其大于各自系统的目标总和。

10 结论

在数据使用上,新兴的数据管理实践和相关分析继续朝着多样化和异质的场景发展。同时,通过 SQL 方式获取数据的关系型数据源仍然是企业获取数据的基本方法。在这种分歧的情况,Apache Calcite 扮点着独特的角色,其不仅能够支持传统、常见的数据处理,也支持其他数据源处理,包括半结构化、流式和地理空间数据。另外,Apache Calcite 专注于灵活、可适配和可扩展的设计哲学也成为一个重要的因素,在大量的开源框架中,使其成为被广泛采用的查询优化器。Apache Calcite 动态且灵活的查询优化器和适配器架构使其能够被嵌入到各种数据处理框架中,包括:Hive,Drill,MapD,Flink。Apache Calcite 支持异质数据处理,同时其关系函数在功能和性能,也在不断得到提升。


发布于: 2021 年 06 月 07 日阅读数: 500
用户头像

余生

关注

还未添加个人签名 2018.04.03 加入

还未添加个人简介

评论

发布
暂无评论
Apache Calcite:异质数据源优化查询框架