Content Entry

Apache Doris 的过去、现在与未来

Published: 2022-01-08 Categories: SQL Tags: Doris OLAP

2021 年 6月 26 号,Apache Doris 社区和 Apache Pulsar 社区在北京联合举办了一场的线下 Meetup 。我作为第一个分享嘉宾,为大家介绍了下 Doris 的历史、现状和一些未来规划。

文章的标题是 《Doris 的过去、现在与未来》,但其实我主要想和大家分享的以下三方面的内容:

  • Doris 是什么,发展历程是什么。
  • 为什么要选择 Doris,Doris 提供了哪些功能。
  • Doris 社区目前正在开展的工作,以及后续的一些发展规划。

什么是 Doris

关于 Doris 的发展历程,ApacheDoris 微信公众号有一篇非常详细的文章,如果大家感兴趣,可以直接前往参阅,这里不再赘述:

Doris简史 - 为分析而生的11年

简要概括一下,Doris 是一款基于 MPP 技术的SQL分析型数据库系统,能够在海量数据的 OLAP 场景下提供毫秒级的查询响应性能。Doris 的实现架构脱胎自 Apache Impala 和 Google Mesa 系统,并进行了大量的改造和优化,最终形成了今天大家看到的这款架构优雅、性能卓越、功能丰富、简单易用的 OLAP 数据库系统。

Doris在数据流中的定位

从上面这张图我们可以大致了解 Doris 在整个大数据处理流程中的位置。上游的各类数据如事务数据库中的数据、埋点数据、日志等,通过一些离线系统、消息系统或流处理系统处理后,再导入到 Doris 中进行存储,而 Doris 可以直接对外提供在线报表、多维分析、大屏等数据查询和展示服务。同时,Doris 也可以作为数据源,被 Spark、Flink 等系统访问进行联邦查询或多源数据处理。而 Doris 本身的分布式查询框架,也可以为一些外部数据源如 MySQL、ElasticSearch等系统提供SQL查询能力。

为什么是 Doris

这部分想为大家分享的是,当我们选择一款分析型数据库时,我们在选择什么? 就我看来,以下4个方面,是用户选择一款数据库产品的关键因素。

为什么选择Doris

01 足够简单

足够简单

一款数据库产品的引入,本质上是提升企业在数据分析和决策方面的效率。而如果数据库系统本身在使用和运维方面有着极高的成本,那么即使这款数据库的性能再优异,依然无法给企业带来整体上的效能提升。所以 Doris 在系统易用性和维护性方面做了大量的工作,来帮助用户降低和数据库本身系统相关的使用和运维成本,从而帮助用户专注于业务逻辑的开发。

Doris 架构

在架构方面,Doris 只有两个主进程模块。一个是 Frontend(FE),可以理解为 Doris 的管控节点,主要负责用户请求的接入、查询计划的解析、元数据的存储和集群管理相关工作。另一个是 Backend(BE),主要负责数据存储、查询计划的执行。这两类进程都是可以横向扩展的。除此之外,Doris 不依赖任何第三方系统(如 HDFS、Zookeeper等等)。这种高度集成的架构设计极大的简化了一款分布式系统的运维成本。

Doris 自身的分布式管理框架,可以自动的管理数据副本的分布、修复和均衡。比如对于副本损坏的情况,Doris会自动感知并进行修复。而对于节点扩容,仅需一条SQL命令即可完成,Doris会自动进行数据分片的均衡,整个过程完全不影响系统服务,无需运维人员进行任何额外的操作。

Doris 在 FE 模块中实现了 MySQL 兼容协议层,这样用户可以使用标准的 MySQL 客户端或各种语言的类库,即可方便的连接到 Doris。同时 Doris 支持标准的 SQL 语言,在方言方面向 MySQL 兼容。不论是简单的单表聚合、排序过滤操作,还是复杂的多表关联、子查询、窗口函数等,都可以通过 SQL 轻松完成,极大降低了用户的迁移和使用成本。

灰度升级

作为一款分布式系统,Doris 的升级方式却极其简单,只需要替换二进制程序,滚动重启集群即可。在设计上,Doris 完全向前兼容,所以也可以通过灰度升级的方式进行新版本的验证和测试。而 Doris 本身的一些失败重试和故障路由,也极大程度的降低了在升级过程中对业务的影响。

02 足够高效

足够高效

选择一款数据库系统,性能必然是重点考虑的因素之一。这一部分我将重点从以下三个方面,来介绍 Doris 性能如此优异的原因:

  • 存储引擎
  • 查询引擎
  • 查询优化器

存储引擎

存储格式

和大多数分析型数据库一样,Doris 也是以列存格式存储数据的。数据按照列进行连续存储,因为类型相同,因此可以获得极高的压缩率,节省磁盘空间。Doris 对不同的列类型还提供了不同的编码方式,如 INT 类型会使用 BitShuffle 的编码方式,而字符串类型会使用字典编码。更进一步的,Doris 还会自动根据列的值来切换编码类型。比如对于字符串类型,如果列值的 distinct 值较多,则不再使用字典编码,而直接切换到 Plain Text 编码,以节省不必要的空间浪费。

从文件组织形式上,Doris 的文件格式和 Parquet 比较类似。一个数据版本会被分割成最大 256MB 一个的 Segment,每个 Segment 对应一个物理文件。Segment 通常分为 Header、DataRegion、IndexRegion、Footer 这几个区域。Data Region 用于按列存储数据,每一列又被分为多个 Page,而 Page 是 Doris 的最小数据存取单元。

Index Region 则负责存储数据的索引。Doris 提供了丰富的索引结构来帮助加速数据的读取和过滤。索引的类型大体可以分为智能索引二级索引两种。其中智能索引是在 Doris 数据写入时自动生成的,无需用户干预。而二级索引是用户可以选择性的在某些列上添加的辅助索引。

智能索引包括前缀稀疏索引MinMax索引两种。

前缀索引

前缀稀疏索引是建立在排序结构上的一种索引。Doris 存储在文件中的数据,是按照排序列有序存储的,Doris 会在排序数据上,每 1024 行创建一个稀疏索引项。索引的 Key 即当前这1024行中,第一行的前缀排序列的值。当用户的查询条件包含这些排序列是,我们可以通过前缀稀疏索引快速的定位到起始行。

前缀索引

MinMax索引是建立在 Segment 和 Page 级别的索引。对于 Page 中的每一列,都会记录在这个 Page 中的最大值和最小值,同样,在 Segment 级别也会对每一列的最大值和最小值进行记录。这样当进行等值或范围查询时,可以通过MinMax索引快速过滤掉不需要读取的行。

Bloom Filter索引

Bloom Filter 是一种需要用户自主选择是否创建的索引。当对某一列创建 Bloom Filter 后,会在 page 级别创建该列的 Bloom Filter 结构,Bloom Filter 是一种使用固定空间的位图来快速判断一个值是否存在的数据结构。这种索引非常适合用于高基数列上的等值查询,比如 UUID。

倒排索引

倒排索引也是一种需要用户自主选择是否创建的索引。该索引也是基于位图的一种索引结构,其索引Key是实际的列值,而 Value 是该值在数据文件中的offset。通过倒排索引,可以很快定位到列值对应的行号,进行快速取数。这种索引比较合适在基数较低的列上进行等值查询的场景,比如城市等。

除了在存储方式和索引结构,Doris 在读取逻辑上也有很多优化。比如上图的延迟物化功能。该功能会先根据有索引的列,定位到一个数据范围,然后再根据有过滤条件的列进行进一步过滤来缩小数据范围,最后再读取其他需要读取的列。这种方式可以很大程度上减少不必要的数据读取,降低查询请求对IO的资源消耗。

查询引擎

查询引擎

Doris 的查询引擎是基于 MPP 的火山模型,是从早期版本的 Apache Impala 演化而来。一个真正的 MPP 执行框架,相对于一些分布式 Gette-Setter 框架,最显著的区别在于其实现了 Exchange 节点。在 Doris 中,一个SQL语句会先生成一个逻辑执行计划,然后根据数据的分布,形成一个物理执行计划。物理执行计划会有多个 Fragment,而 Fragment 直接的数据传输则是由 Exchange 节点完成的。有了 Exchange 节点,最大的收益是整个查询框架拥有了数据 Reshuffle 的能力,通过这个能力,查询的计算节点不在局限于数据的存储节点,从而能够更好的利用多节点资源进行并行数据处理。

节点间并行

如上图,物理执行计划中的 Fragment 会被发往多个 BE 节点并行执行。不同 Fragment 之间的数据通过 Exchange 节点,根据规则 Shuffle 到对应的上层 Fragment 中。

节点内并行

除了节点间并行,Doris 还支持在一个节点内部继续拆分 Fragment,从而进行节点内的并行执行,以充分利用一个节点上的多 CPU 资源。

除了整体的执行框架通过并行设计来提高查询效率外,Doris 还对很多具体的查询算子进行了优化。

自适应聚合算子

比如上图中的 Aggregate Node 聚合算子。该算子用于计算 group sum 一类的查询。在 Doris 中,聚合算子会被拆分成两级聚合。第一级聚合会在数据所在节点先进行一次本地聚合,来减少发送到第二层聚合时需要传输的数据量,而第二层聚合会将相同的 Key 汇聚到同一个节点,进行最终的聚合计算。

在此基础上,Doris 还实现了自适应的聚合算法。首先我们要知道,聚合算子是一种阻塞型的算子,他需要等到全部数据处理完成后,才会将数据发送给上层节点。而自适应聚合算法的意思是,在第一级聚合算子中,如果发现数据的聚合效果很低,即使聚合后也无法有效降低需要传输的数据量时,则会自动的停止第一级聚合,而将算子转换为一个非阻塞的流式算子,直接将读取到的数据发送到上层节点,从而避免了不必要的阻塞等待时间。

针对 Join 算子,Doris 也进行了大量优化,其中 Runtime Filter 是其中很重要的一种优化方式。

Runtime Filter

在两个表的 Join 操作中,我们通常将右表称为 BuildTable,而左表称为 ProbeTable。在实现上,会首先读取右表的数据,在内存中构建一个 HashTable(Build)。之后开始读取左表的每一行数据,并在 HashTable 中进行连接匹配,来返回符合连接条件的数据(Probe)。通常来说,左表的数据量会大于右表的数据。

而 Runtime Filter 的设计思路,是在右表构建 HashTable 的同时,为连接列生成一个过滤结构。之后把这个过滤列结构下推给左表。这样一来,左表就可以利用这个过滤结构,对数据进行过滤,从而减少 Probe 节点需要传输和比对的数据量。这种过滤结构被称为 Runtime Filter。

针对不同的数据,Doris 也实现了不通过的 Filter 类型,如 In Predicate,Bloom Filter 和 Min Max。用户可以根据不同场景进行选择。

Runtime Filter

同时,Runtime Filter 可以适用于大部分 Join 场景,包括节点的自动穿透,将 Filter 穿透下推到最底层的扫描节点,或者在分部署 Shuffle Join 中,将多个节点产生的 Filter 进行合并后再下推等

Runtime Filter 效果

上图是在 SSB 测试集中,Runtime Filter 功能开启前后的查询延迟对比(越低越好)。

查询优化器

除了查询执行层方面的优化,Doris 在查询优化器方面也做了大量的工作。Doris 查询优化器能够同时进行基于规则和基于代价的查询优化。在基于规则的查询优化方面,Doris 包括但不限于以下优化规则:

常量折叠

常量折叠可以预先将常量表达式进行计算,计算后的结果有助于规划器进行分区分桶裁剪、以及执行层利用索引进行数据过滤等。

子查询改写

子查询改写会将子查询改写为 Join 操作,从而利用 Doris 在 Join 上做的一系列优化来提升查询效率。

抽取公共表达式

提取公共表达式可以将SQL中的一些析取范式转换成和取范式,而和取范式通常是对查询执行引擎友好的,可以进行条件的拆分来进行不同级别的谓词下推和计算工作。

智能预过滤

智能预过滤是提取公共表达式的高级版本,他可以将SQL中的析取范式,转换成和取范式并提炼出公共的条件部分。这些公共条件可以预先过滤部分数据,从而减少数据处理量。

谓词下推

谓词下推也是查询优化器常见的优化手段,Doris 中的谓词下推不仅可以穿透算子,更能进一步的下推到存储引擎,利用数据索引进行数据的过滤。

而在基于代价的优化方面,Doris 主要针对 Join 算子进行了大量的优化。

Join Reorder

Join Reorder 功能可以通过一些表的统计信息,自动的调整 Join 的顺序。而 Join 顺序的调整,会显著降低 Join 操作中生成的中间数据集的大小,从而加速查询的执行。

Colocation Join

Colocation Join 可以利用数据的分布情况,将原本需要 Shuffle 后才能进行 Join 的数据,在本地即可完成 Join 操作。从而避免了 Shuffle 时大量的网络数据传输。

Bucket Join

Bucket Join 是 Colocation Join 的通用版本。在 Colocation Join 中,用户需要在建表时就指定表的分布,来保证需要关联查询的若干个表有相同的数据分布。而 Bucket Join 会更智能的自动判断 SQL 中的关联条件和数据分布之间的关系,将原本需要同时 Shuffle 的左右两张表的数据的操作,变成仅将右表数据 Shuffle 到左表所在的节点,从而减少 Shuffle 的数据量。

以上就是 Doris 在存储、执行、优化器方面的一些重点优化和实现的介绍。

03 功能丰富

功能丰富

除了卓越的性能外,Doris 也提供了非常丰富的功能来帮助业务适应不同的应用场景。下面我将重点介绍一些 Doris 的特色功能。

高并发-裁剪

Doris 是能够支持高并发现在服务场景的,单节点可以支持上千QPS的查询请求。高并发的支持得益于 Doris 的分区分桶裁剪功能。利用查询条件,Doris 可以将查询固定到极少数分片上,从而显著降低单个查询对系统资源的消耗,提升集群整体的并发查询能力。

高并发-缓存

同时,Doris 还支持 SQL 级别和 Partition 级别的 Cache。其中 SQL Cache 以 SQL 语句的 Hash 值作为 Key,直接缓存 SQL 结果,非常适合更新频率不高,但是查询非常频繁的场景。而 Partition 级别的 Cache,会智能的将 SQL 结果中不同分区的结果数据缓存起来,之后的查询,可以利用已缓存分区的数据加上新分区实时查询的数据得到最终的结果,从而降低重复数据的实时查询需求,减少对系统资源的消耗。

高吞吐

作为一款 MPP 数据库,Adhoc 这类高吞吐的即席查询和库内 ETL 场景也是 Doris 的强项。Doris 能够支持负载 SQL 语法,包括窗口函数、Grouping Set 等高级语法功能,同时还可以通过 UDF 或 UDAF 来自定义拓展 Doris 的功能。在TB级别数据上,Doris 可以部分代替 Hive 等离线系统的功能,使得用户在一套数据库中满足所有需求。

Doris 支持 Bitmap 数据类型。这一数据类型利用位图来存储存储整型数据,并且可以通过位图进行一些集合类操作。

Bitmap-精确去重

Bitmap 可以应用于高基数精确去重场景。传统的实时计算去重数据的算法,需要在内存中构建 Hash 表来进行数据去重,在基数非常高的情况下,会占用大量的内存。而使用 Bitmap,可以将数值类型转换成位图上的 0 和 1,从而极大的降低内存开销,并且对于去重计算,只需要将多个 bitmap 求交集后计算 1 的个数即可,从而达到在有限的内存开销情况下,进行快速的高基数精确去重计算。

Bitmap-用户画像

在用户画像场景,用户可以使用 Bitmap 来存储用户 ID,采用倒排的方式存储用户和标签。在人群圈存场景下,可以通过位图的集合运算快速获取不同标签组合的人群包。

Doris 也内置了很多 Bitmap 相关的函数,用于计算漏斗、留存等。比如通过intersect_count() 函数就可以方便的计算用户的留存情况。

物化视图

物化视图也是 Doris 的核心特点之一。在 Doris 中,用户可以使用明细数据模型存储明细数据,之后,可以在明细数据上,选择任意维度和指标创建聚合物化视图,如 SUM、MIN、MAX、COUNT 等。Doris 会保证明细表和物化视图中数据的完全一致。同时,物化视图对用户查询是透明的,系统会自动根据查询语句中的模式,匹配到最合适的物化视图进行查询。通过物化视图功能,用户可以在一张表上统一明细和聚合模型,并且加速某些固定模式的查询响应。

数据更新

Doris 还支持基于主键的数据更新。通过 Unique Key 数据模型,用户可以对数据基于主键进行更新。在实现层面,Doris 采用 Merge-on-Read 的方式提供更新后的数据,此外,用户还可以使用REPLACE_IF_NOT_NULL这种聚合方式,实现部分列更新的需求。Doris 还支持友好的 Update 语句。

基于 Unique Key 模型,Doris 还通过 Marked Delete 和 Sequence Column 等功能,可以实现对上游 TP 数据库更新数据的同步操作,并且不仅能够保证事务的原子性,还可以保证数据同步的顺序性。

04 开源开放

一款开源产品,除了代码开放外,更重要的是需要一个活跃的社区,开发者开发产品,用户进行使用和反馈,帮助提升产品质量和迭代速度,开发者也不断地收集用户的使用场景来提供更多的功能,从而达到一个正向的循环。

开源开放

Doris 目前每周的活跃贡献者能够达到20多人,Contributor数量也已经到达 160 多,虽然相比于 Pulsar 或 TiDB 等成熟社区而言还有差距,但是依然处于一个高速发展的阶段。详细随着更多开发者和用户的加入,Doris 社区会逐步发展壮大。

Doris 的进行时和未来式

最后向大家介绍一下目前 Doris 社区正在进行的一些工作,以及未来的发展方向。

首先还是性能。目前社区正在进行全新的向量化引擎的开发工作,这一部分我们也参考了 Clickhouse 的非常多优秀的代码实现。在工程实现层面,使用提升Cache 亲和度、减少虚函数调用、分支预测、SIMD指令集等方式显著提升了 Doris 各类算子的运算速度。同时,更精细的内存管理也有助于减少内存分配时的系统调用和锁竞争。社区预计在 7 月份会合入向量化引擎的第一个版本,能够支持单表的聚合排序查询。而后续将会开展更多的工作,也欢迎社区的开发者一同参与。

冷热分级存储

降低成本也是 Doris 的发展方向之一。随着对象存储等廉价高可靠存储设备正在成为大数据体系的标配,一些历史数据或冷数据可以迁移到对象存储上,从而降低对本地磁盘的消耗。并且可以通过缓存策略提供对历史数据的快速查询

资源隔离

资源隔离也是很多用户的需求。Doris 计划利用资源标签的方式实现统一集群内节点级别的资源隔离。并且可以指定将表的副本分别存放在不同的资源组中,并支持使用指定资源组中的节点进行数据查询,从而可以在一个集群内,将在线和离线请求进行物理隔离,减少业务之间的干扰。

云原生

最后,云原生肯定是 Doris 的最终形态。Doris 目前已经进行了和 Flink、Iceberg 等新一代大数据组件的打通工作。并且也开展了存算分离的调研和原型验证。相信 Doris 会成为一款面向多场景、成熟、高效的分析型数据库,来帮助更多的企业快速高效的解决数据分析需求。

Doris 版本说明

Apache Doris 目前的发版速度平均为 1-2 个季度一个位版本,为了保证 Doris 的迭代速度,并且帮助用户快速的享受到 Doris 的新功能和 Bug 修复,百度提供了基于 Doris 的发行版本 Palo。百度发行版沿用 Doris 的版本号规则,并且以 3 位版本的形式对外提供源码和预编译版本。Palo 版本包括 Apache Doris 的全部代码更新,并且和 Doris 完全兼容。

comments loading