Apache Doris是一个开源的MPP(Massively Parallel Processing,大规模并行处理)架构的分析型数据库,以高性能、易用性、亚秒级查询响应著称,近年来在国内越来越火,百度、美团、小米、京东、字节跳动等很多大厂都在用,社区也非常活跃。

我最近在研究Doris的源码和架构,因为公司在做数据平台的技术选型,需要深入了解各个OLAP数据库的原理和优缺点。我读了Doris的官方文档、设计文档、部分源码,也做了一些性能测试,对它的底层机制有了比较深入的理解。

本文从整体架构、存储引擎、查询引擎、写入流程、查询优化、高可用、和其他OLAP数据库的对比等方面,深入剖析Doris的底层原理,聊聊它为什么能这么快,它的设计有什么巧妙之处,以及它的局限性在哪里。希望能给想了解Doris的朋友一些参考,也欢迎大家交流指正。

先说明一下:本文基于Doris 0.15版本(2021年的版本),后续版本可能有架构变化和功能增强,具体以最新版本为准。我不是Doris的核心开发者,只是一个研究者和使用者,理解可能有偏差,甚至有错误,仅供参考。

一、Doris是什么,解决什么问题

在深入原理之前,先简单介绍一下Doris是什么,它解决什么问题。

Doris最初是百度在2017年开源的一个分析型数据库,后来贡献给Apache基金会,成为Apache的顶级项目。它的定位是一个MPP架构的、支持实时分析的OLAP(Online Analytical Processing,联机分析处理)数据库,主要用于解决大数据量下的多维分析、实时报表、即席查询等场景。

简单来说,Doris解决的问题是:当你的数据量很大(几十亿到几百亿行),查询维度很多,要求查询响应快(亚秒级到秒级),同时还要支持实时数据写入和查询,传统的数据库(MySQL、PostgreSQL)扛不住,Hive、Spark SQL又太慢,这时候就需要Doris这样的OLAP数据库。

Doris的主要特点:

  • MPP架构:数据分布式存储,查询并行执行,横向扩展能力强。
  • 列式存储:按列存储数据,查询时只读取需要的列,IO效率高,压缩比高。
  • 亚秒级查询:通过各种优化(预聚合、索引、向量化执行、CBO优化器等),大部分查询能在亚秒级返回结果。
  • 实时写入:支持实时数据写入,写入后立即可查,延迟在秒级。
  • 兼容MySQL协议:使用MySQL协议,用户可以用MySQL客户端、BI工具直接连接,学习成本低。
  • 易运维:架构简单,只有两类进程(FE和BE),不依赖Hadoop、Spark等其他组件,部署和运维简单。

这些特点,让Doris在很多场景下都非常好用,尤其是企业级的数据分析、报表、看板等场景。下面我们就深入看看,Doris是怎么实现这些特点的。

二、整体架构:FE和BE两类进程

Doris的架构非常简洁,只有两类进程:FE(Frontend)BE(Backend)

  • FE(Frontend):前端节点,负责接收用户请求、解析SQL、查询规划、元数据管理、调度等。FE是无状态的,可以水平扩展,通过一致性协议(BDB-JE)选主,保证元数据的一致性。
  • BE(Backend):后端节点,负责数据存储、查询执行、数据写入等。BE是有状态的,存储实际的数据,数据在多个BE之间分布式存储,通过副本机制保证高可用。

就这么两类进程,没有其他依赖,部署非常简单,这也是Doris易用性的一个重要体现。对比一下,ClickHouse需要自己处理分布式表和副本,配置比较复杂;StarRocks(Doris的一个分支,后来独立发展)的架构和Doris类似,也是FE+BE。

下面分别看看FE和BE的职责。

FE的主要职责:

  1. 接收用户请求:FE兼容MySQL协议,用户用MySQL客户端连接FE,发送SQL语句。
  2. SQL解析和分析:FE解析SQL,进行语法分析、语义分析,生成逻辑执行计划。
  3. 查询优化:FE的CBO(Cost-Based Optimizer,基于代价的优化器)对逻辑执行计划进行优化,选择最优的执行计划,比如join顺序、join算法、是否使用预聚合表等。
  4. 查询调度:FE把优化后的执行计划拆分成多个Fragment,调度到对应的BE上执行,并协调各个BE之间的数据流转。
  5. 元数据管理:FE管理数据库、表、分区、副本等元数据,元数据存储在FE的内存中,通过BDB-JE持久化和同步。
  6. 数据导入调度:FE负责数据导入任务的调度,把导入任务分配给对应的BE。

FE是无状态的,可以部署多个,做负载均衡。多个FE之间通过BDB-JE(一个嵌入式的高可用key-value存储)同步元数据,选一个主FE,其他FE是从FE,主FE负责写操作,从FE负责读操作,主FE挂了会自动选新的主。

BE的主要职责:

  1. 数据存储:BE存储实际的数据,数据按表、分区、分桶(Tablet)分布式存储在多个BE上,每个Tablet有多个副本(默认3副本),分布在不同的BE上。
  2. 查询执行:BE接收FE调度的查询任务,执行具体的计算(扫描、过滤、聚合、join等),并把中间结果返回给FE或其他BE。
  3. 数据写入:BE接收数据写入请求,把数据写入内存中的MemTable,达到阈值后flush到磁盘,生成数据文件。
  4. 数据Compaction:BE后台定期做Compaction,把多个小的数据文件合并成大的,删除已标记删除的数据,优化查询性能。
  5. 副本管理:BE负责副本的创建、同步、修复,保证数据的一致性和高可用。

BE是有状态的,存储实际的数据,所以BE的扩展需要考虑数据的均衡。Doris支持在线扩容,新增BE后,会自动把部分数据迁移到新BE上,保持数据均衡。

整个架构的数据流是这样的:

  • 写入:用户 → FE(接收、调度) → BE(写入、存储)
  • 查询:用户 → FE(解析、优化、调度) → BE(并行执行、返回结果) → FE(汇总结果) → 用户

这个架构简洁高效,FE负责"大脑"的工作(解析、优化、调度),BE负责"体力"的工作(存储、计算),职责清晰,易于扩展和运维。

三、存储引擎:列式存储、分区、分桶、副本

Doris的存储引擎是它高性能的基础之一。Doris采用列式存储,支持分区和分桶,数据多副本存储,下面我们详细看看。

1. 数据模型:行存 vs 列存

Doris默认采用列式存储(也支持行存,但主要用列存)。列式存储是什么意思呢?就是把表的数据按列来存储,同一列的数据存在一起,而不是像行存那样把一行的所有列存在一起。

比如有一张用户表,有id、name、age、city四列:

  • 行存:id1,name1,age1,city1, id2,name2,age2,city2, ...
  • 列存:id1,id2,id3,... | name1,name2,name3,... | age1,age2,age3,... | city1,city2,city3,...

列式存储的好处:

  • 查询效率高:分析查询通常只需要少数几列,列式存储只读取需要的列,不需要读取整行,IO量小。比如查询"SELECT city, COUNT(*) FROM user GROUP BY city",只需要读取city列,不需要读取id、name、age列,IO量减少75%。
  • 压缩比高:同一列的数据类型相同,数据相似度高,压缩效果好。比如city列,很多重复的值,用字典编码或RLE编码,压缩比能达到5-10倍,甚至更高。压缩比高,存储成本低,而且查询时读取的数据量更小,更快。
  • 向量化执行友好:列式存储的数据是连续的同类型数据,非常适合向量化执行(SIMD指令),一次处理一批数据,计算效率高。

当然,列式存储也有缺点,比如点查(根据主键查一行)效率不如行存,因为需要从多个列文件中拼接一行数据。但Doris的定位是OLAP分析,主要是批量查询和聚合,列式存储的优势远大于劣势。

2. 分区(Partition)

Doris支持分区,把一张大表按某个列(通常是时间列)分成多个分区,每个分区独立存储。比如按天分区,每天的数据一个分区。

分区的好处:

  • 查询裁剪:查询时如果带分区列的过滤条件(比如WHERE dt = '2021-10-01'),只需要扫描对应的分区,不需要扫描全表,大大减少数据扫描量。
  • 数据管理方便:可以按分区删除数据(比如删除30天前的分区),不需要逐行删除,效率高。也可以按分区导入数据,导入失败只影响当前分区。
  • 冷热数据分离:可以把热数据(最近的)存在SSD,冷数据(历史的)存在HDD,降低存储成本。

Doris支持范围分区(Range Partition)和列表分区(List Partition),最常用的是按时间的范围分区。

3. 分桶(Bucket / Tablet)

分区之后,每个分区还可以进一步分桶,按某个列(通常是高基数字列,比如用户ID)哈希分成多个桶(Tablet),每个桶是数据分布和副本的基本单位。

比如一个分区有1000万行数据,分成10个桶,每个桶100万行,每个桶有3个副本,分布在不同的BE上。

分桶的好处:

  • 并行查询:查询时,多个桶可以在多个BE上并行扫描和计算,提高查询并发度。
  • 数据均衡:数据按哈希均匀分布在多个桶和多个BE上,避免数据倾斜。
  • 高可用:每个桶有多个副本,分布在不同的BE上,某个BE挂了,其他BE上的副本还能提供服务。

分桶列的选择很重要,一般选择高基数、查询经常用做过滤或join的列,这样数据分布均匀,查询效率高。如果分桶列基数太低(比如性别,只有男和女两个值),会导致数据倾斜,某些桶数据量特别大,影响查询性能。

4. 副本(Replica)

Doris采用多副本机制保证高可用,默认每个Tablet有3个副本,分布在不同的BE上(也可以配置2副本或1副本,根据数据重要性和成本考虑)。

副本之间通过Paxos协议(Multi-Paxos)保证一致性,一个副本是主副本(Leader),其他是从副本(Follower)。写入数据时,先写主副本,主副本同步到从副本,多数副本写成功后,返回写入成功。查询时,可以读主副本,也可以读从副本(默认读主,也可以配置读从,提高读并发)。

多副本的好处:

  • 高可用:某个BE挂了,其他BE上的副本还能提供服务,数据不丢失,服务不中断。
  • 读扩展:可以读从副本,提高读并发能力。
  • 数据安全:多副本存储,避免单点故障导致数据丢失。

当然,多副本也意味着存储成本翻倍(3副本就是3倍存储),但对于分析型数据库来说,数据安全和高可用更重要,存储成本相对可控(列式存储压缩比高,实际存储成本没有那么高)。

5. 数据文件结构:Segment、Column、Page

Doris的每个Tablet的数据,存储在磁盘上的多个Segment文件中。每个Segment文件包含多个列的数据,每列的数据又分成多个Page(数据页),Page是IO和压缩的基本单位。

具体结构:

  • Tablet:一个分桶的数据,包含多个Segment文件。
  • Segment:一次Compaction生成的数据文件,包含所有列的数据。
  • Column:Segment中的一列数据,包含多个Page。
  • Page:数据页,是IO和压缩的基本单位,每个Page存储一批数据,压缩后存储。
  • Index:每列有索引(Zone Map索引、前缀索引等),用于快速定位数据。

这种分层的文件结构,既能高效存储大量数据,又能通过索引和分页快速查询需要的数据,是Doris高性能的基础之一。

四、写入流程:MemTable、Flush、Compaction

Doris支持实时写入,写入后立即可查,延迟在秒级。它的写入流程是怎样的呢?下面我们详细看看。

1. 写入方式

Doris支持多种写入方式:

  • Stream Load:HTTP流式写入,适合实时数据导入,单条或小批量写入,延迟低。
  • Broker Load:通过Broker从HDFS、S3等外部存储批量导入数据,适合大批量离线导入。
  • Routine Load:例行导入,持续从Kafka消费数据导入,适合实时数据流。
  • Insert Into:SQL方式插入,兼容MySQL的INSERT语法,适合小批量测试或简单导入。
  • Spark Load / Flink Connector:通过Spark或Flink批量导入,适合大数据量的离线导入。

不同的写入方式适用于不同的场景,但底层的写入流程是类似的,都是把数据写入BE的内存,然后flush到磁盘。

2. 写入流程详解

以Stream Load为例,写入流程大致如下:

  1. 用户发送HTTP请求:用户把数据通过HTTP POST发送给FE(或BE),FE接收请求,做鉴权、解析、调度。
  2. FE调度写入:FE根据表的分区和分桶信息,计算每条数据应该写入哪个Tablet,然后把写入任务调度给对应Tablet的主副本所在的BE。
  3. BE写入MemTable:BE接收数据,把数据写入内存中的MemTable(内存表)。MemTable是一个内存中的数据结构,按主键排序(如果是主键模型),支持写入和查询。
  4. MemTable Flush:当MemTable的大小达到阈值(默认100MB),或者写入时间达到阈值,MemTable会被flush到磁盘,生成一个Segment文件。flush是异步的,不阻塞写入。
  5. 数据立即可查:数据写入MemTable后,就可以被查询到了(查询时同时扫描MemTable和磁盘上的Segment文件),所以写入后立即可查,延迟很低。
  6. Compaction:随着写入的进行,会生成很多小的Segment文件,BE后台会定期做Compaction,把多个小Segment合并成大Segment,同时删除已标记删除的数据,优化查询性能。

这个写入流程的设计,既保证了写入的低延迟(写入内存就返回),又保证了查询的实时性(内存数据立即可查),还通过Compaction保证了查询性能(合并小文件,减少文件数量)。

3. Compaction机制

Compaction是Doris写入流程中非常重要的一个环节,也是影响性能的关键因素。因为写入是小批量、持续的,会生成很多小的Segment文件,如果不合并,查询时需要扫描很多小文件,效率很低,而且文件太多也会影响文件系统的性能。

Doris的Compaction分为两种:

  • Cumulative Compaction(累积Compaction):把多个小的Segment文件合并成一个中等大小的Segment,减少文件数量。这个过程比较频繁,合并的是最近写入的小文件。
  • Base Compaction(基础Compaction):把累积Compaction生成的中等Segment和基础的大Segment合并,生成一个更大的基础Segment,同时删除已标记删除的数据(在更新和删除场景下,数据是标记删除的,Base Compaction时才真正删除)。这个过程比较耗时,频率较低。

Compaction是后台异步执行的,不影响前台的写入和查询,但会消耗CPU和IO资源。如果写入量很大,Compaction速度跟不上写入速度,就会导致小文件堆积,查询性能下降,这就是所谓的"Compaction压力"。

Doris有一些机制来缓解Compaction压力,比如动态调整Compaction的并发度和资源、限制写入速度、自动合并小文件等,但在超高写入量的场景下,Compaction仍然可能成为瓶颈,需要合理配置和优化。

4. 数据模型:明细、聚合、主键

Doris支持三种数据模型,不同的模型适用于不同的场景,写入和查询的行为也不一样:

  • 明细模型(Duplicate Key Model):默认模型,数据是明细的,允许重复,不做预聚合。适用于原始数据存储、明细查询等场景。写入时直接追加,查询时扫描所有数据。
  • 聚合模型(Aggregate Key Model):按维度列聚合,指标列按聚合函数(SUM、COUNT、MAX、MIN等)预聚合。适用于报表、看板等聚合查询场景。写入时,相同维度的数据会在MemTable和Compaction时聚合,查询时直接读预聚合的数据,速度快,但写入时需要聚合,开销大一些。
  • 主键模型(Unique Key Model):按主键去重,相同主键的数据,新数据覆盖旧数据。适用于需要更新的场景,比如用户画像、订单状态更新等。写入时,相同主键的数据在Compaction时合并(旧数据标记删除,保留新数据),查询时只返回最新的数据。

不同的数据模型,写入流程和Compaction策略略有不同,但整体架构是一样的。选择合适的数据模型,对性能影响很大,需要根据业务场景选择。

五、查询引擎:MPP、向量化、CBO优化器

Doris的查询引擎是它高性能的另一个关键。Doris采用MPP架构,支持向量化执行,有CBO优化器,下面我们详细看看。

1. MPP执行模型

MPP(Massively Parallel Processing,大规模并行处理)是Doris查询执行的基本模型。简单来说,就是把一个查询拆分成多个子任务,分发到多个BE上并行执行,每个BE处理一部分数据,最后汇总结果。

Doris的查询执行流程:

  1. FE接收SQL,解析、优化,生成分布式执行计划。
  2. FE把执行计划拆分成多个Fragment(执行片段),每个Fragment负责一部分计算。
  3. FE把Fragment调度到对应的BE上执行,BE之间通过数据流式传输(Exchange)交换中间结果。
  4. 各个BE并行执行,最后把结果汇总到FE(或一个BE),返回给用户。

比如一个简单的聚合查询"SELECT city, COUNT(*) FROM user GROUP BY city":

  • 假设有10个桶,分布在5个BE上。
  • FE调度5个BE,每个BE扫描2个桶的数据,做局部聚合(每个BE内部按city分组计数)。
  • 每个BE把局部聚合结果发送给一个汇总BE(或FE),汇总BE做最终聚合(把不同BE的相同city的count加起来)。
  • 汇总结果返回给用户。

这样,原本需要一个节点处理1000万行数据,现在5个节点并行处理,每个节点处理200万行,查询速度理论上能提升5倍(实际受网络、聚合开销等影响,可能达不到线性扩展,但提升还是很明显的)。

MPP架构的好处是横向扩展能力强,数据量增大时,增加BE节点就能提升查询性能。而且MPP适合复杂的分析查询(多表join、多层聚合、子查询等),能充分利用集群的计算资源。

2. 向量化执行(Vectorized Execution)

向量化执行是Doris 0.15版本引入的重要特性(之前是火山模型,逐行处理),也是Doris性能提升的关键。

传统的火山模型(Volcano Model)是逐行处理的,每个算子一次处理一行数据,调用next()方法获取下一行。这种模型的优点是灵活,容易实现各种算子,但缺点是函数调用开销大,CPU缓存命中率低,无法利用SIMD指令,性能不高。

向量化执行是批量处理的,每个算子一次处理一批数据(比如1024行),数据按列存储在连续的内存中(Column),算子对整批数据做计算。这种模型的优点:

  • 函数调用开销小:一次处理一批数据,函数调用次数大大减少。
  • CPU缓存命中率高:同列数据连续存储,缓存友好。
  • 支持SIMD指令:同类型数据连续存储,可以用SIMD指令(SSE、AVX等)一次处理多个数据,计算效率成倍提升。
  • 分支预测友好:批量处理,分支预测更准确。

Doris的向量化执行是基于列式数据结构的,每个算子接收和输出的都是列式的批量数据(Block,包含多个Column),算子内部对整批数据做向量化计算。比如过滤算子,对整批数据的某一列做比较,生成一个过滤掩码,然后根据掩码过滤数据;聚合算子,对整批数据做分组聚合,用哈希表批量处理。

向量化执行的性能提升非常明显,尤其是在计算密集型的查询(聚合、join、表达式计算等)中,性能能提升几倍甚至十几倍。Doris 0.15版本默认开启向量化执行,也是这个版本性能大幅提升的主要原因。

3. CBO优化器

CBO(Cost-Based Optimizer,基于代价的优化器)是Doris查询优化的核心。CBO的作用是,对于一个SQL查询,生成多个可能的执行计划,估算每个计划的代价(CPU、IO、网络等),选择代价最低的执行计划。

Doris的CBO优化器是基于Apache Calcite开发的(后来做了很多定制和优化),支持:

  • 逻辑优化:谓词下推、列裁剪、子查询改写、常量折叠、外连接转内连接等。
  • 物理优化:join顺序选择(多表join时,选择最优的join顺序)、join算法选择(Hash Join、Nested Loop Join、Broadcast Join等)、聚合方式选择(本地聚合+全局聚合、两阶段聚合等)、是否使用预聚合表(物化视图)等。
  • 代价估算:基于表的统计信息(行数、列的基数、数据分布、NULL值比例等),估算每个执行计划的代价,选择最优的。

CBO优化器对于复杂查询(多表join、子查询、复杂聚合)的性能影响很大,一个好的执行计划和一个差的执行计划,性能可能相差几十倍甚至上百倍。Doris的CBO优化器在不断完善,支持的优化规则越来越多,代价估算也越来越准确。

当然,CBO优化器的效果依赖于统计信息的准确性,如果统计信息不准确(比如数据更新后没有重新收集统计信息),优化器可能选错执行计划,导致查询性能下降。所以Doris支持自动和手动收集统计信息,保证统计信息的准确性。

4. 索引机制

Doris有多种索引,用于加速查询:

  • 前缀索引(Prefix Index):Doris的表是按排序键排序存储的,排序键的前36个字节会建立前缀索引,查询时如果过滤条件包含排序键的前缀,可以用前缀索引快速定位数据,类似MySQL的聚簇索引。
  • Zone Map索引:每列的每个Page(数据页)都有Zone Map索引,记录这个Page中该列的最大值、最小值、NULL值数量等。查询时,如果过滤条件和Zone Map不匹配(比如过滤条件是age > 30,但某个Page的max(age) = 25),可以直接跳过这个Page,不需要读取,大大减少IO量。
  • Bloom Filter索引:可以给高基数的列建Bloom Filter索引,用于快速判断某个值是否存在,适合等值查询(比如WHERE id = 123)。Bloom Filter是一种概率数据结构,能快速判断"值一定不存在"或"可能存在",空间效率很高。
  • Bitmap索引:可以给低基数的列建Bitmap索引,适合多条件组合查询(比如WHERE city = '北京' AND age > 30 AND gender = '男'),多个Bitmap可以快速做与或运算,定位符合条件的行。
  • 倒排索引:新版本的Doris支持倒排索引,用于文本搜索和多条件组合查询,类似搜索引擎的倒排索引。

这些索引,配合列式存储和分区裁剪,能大大减少查询时的数据扫描量,提升查询性能。合理设计排序键和索引,是Doris性能优化的重要手段。

六、查询优化:预聚合、物化视图、Colocate Join

除了上面提到的存储引擎和查询引擎的优化,Doris还有一些高级的查询优化特性,进一步提升查询性能。

1. 预聚合(Aggregate Model)

前面提到,Doris的聚合模型(Aggregate Key Model)会在写入时就对数据做预聚合,相同维度的数据在MemTable和Compaction时就聚合好了。查询时,如果查询的维度和指标和预聚合的维度匹配,可以直接读取预聚合的数据,不需要再做聚合,查询速度非常快。

比如一张销售表,按日期、城市、商品维度预聚合了销售额和销量,查询"SELECT 日期, 城市, SUM(销售额) FROM 销售表 GROUP BY 日期, 城市"时,直接读取预聚合的数据,不需要扫描明细数据,性能提升明显。

预聚合的代价是写入时需要聚合,写入开销大一些,而且维度是固定的,如果查询的维度和预聚合的维度不匹配,就不能利用预聚合。所以聚合模型适合维度固定、查询模式固定的报表场景。

2. 物化视图(Materialized View)

物化视图是预聚合的升级版,它可以在一张基表上创建多个物化视图,每个物化视图可以有不同的维度、不同的聚合方式、不同的排序键,满足不同的查询模式。

比如一张用户行为明细表,可以创建:

  • 物化视图1:按日期、用户ID聚合,查询用户日活。
  • 物化视图2:按日期、页面聚合,查询页面访问量。
  • 物化视图3:按日期、来源聚合,查询来源分布。

查询时,CBO优化器会自动判断查询是否匹配某个物化视图,如果匹配,就自动路由到物化视图查询,不需要用户手动指定,对用户透明。

物化视图的好处是灵活,可以针对不同的查询模式创建不同的物化视图,查询性能好。代价是占用更多存储空间(每个物化视图都存一份数据),写入时需要更新所有物化视图,写入开销大。所以物化视图适合查询模式多样、查询性能要求高、写入量不是特别大的场景。

3. Colocate Join(同组Join)

多表join是分析查询中常见的操作,也是性能瓶颈之一。传统的分布式join(Shuffle Join)需要把两张表的数据按join key哈希分发到所有节点,数据传输量大,网络开销大。

Doris支持Colocate Join(同组Join),把需要经常join的表,按相同的分桶列和分桶数,把对应的数据分桶存储在同一个BE组上。这样join的时候,对应桶的数据在同一个BE上,不需要跨节点传输数据,直接在本地join,大大减少网络开销,提升join性能。

比如订单表和用户表,都按userid分桶,分桶数相同,对应桶的数据存在同一个BE组上。查询"SELECT * FROM 订单 JOIN 用户 ON 订单.userid = 用户.user_id"时,每个BE只需要join本地的订单桶和用户桶,不需要数据传输,性能很好。

Colocate Join的限制是,同组的表必须有相同的分桶列、分桶数、副本数,而且对应桶的副本要在同一个BE组上,扩容和迁移时需要保持对应关系。所以Colocate Join适合那些经常join、join key固定的表,比如事实表和维度表的join。

4. Runtime Filter(运行时过滤)

Runtime Filter是一种动态的查询优化技术,在join查询中,先扫描小表,根据小表的join key生成一个过滤条件(Bloom Filter或Min/Max),然后在扫描大表的时候,用这个过滤条件提前过滤掉不匹配的数据,减少大表的扫描量和后续join的数据量。

比如"SELECT * FROM 大表 JOIN 小表 ON 大表.id = 小表.id",小表只有1000行,大表有1亿行。先扫描小表,得到1000个id,生成一个Bloom Filter,然后扫描大表的时候,用这个Bloom Filter过滤,只有id在Bloom Filter中的行才需要参与join,这样大表大部分数据都被过滤掉了,join的数据量大大减少,性能提升明显。

Doris支持Runtime Filter,在多表join查询中能自动应用,对大表join小表的场景性能提升很大。

七、高可用和运维

Doris的高可用和运维设计也比较完善,下面简单看看。

1. FE高可用

FE是无状态的,元数据存储在BDB-JE中,多FE部署时,通过BDB-JE的一致性协议同步元数据,选一个主FE,主FE挂了自动选新的主,服务不中断。FE的扩展很简单,新增FE节点,加入集群即可。

2. BE高可用

BE是有状态的,数据多副本存储(默认3副本),分布在不同的BE上。某个BE挂了,其他BE上的副本还能提供服务,数据不丢失。FE会检测到BE宕机,自动把宕机BE上的副本在其他BE上重建,恢复副本数,保证高可用。

BE的扩展也支持在线扩容,新增BE后,FE会自动调度,把部分数据迁移到新BE上,保持数据均衡,迁移过程不影响前台服务。

3. 易运维

Doris的运维比较简单:

  • 架构简单:只有FE和BE两类进程,不依赖其他组件(Hadoop、ZooKeeper等),部署简单。
  • 在线扩缩容:支持在线增加和减少FE、BE节点,不影响服务。
  • 监控完善:提供丰富的监控指标(查询性能、写入性能、Compaction、副本状态等),可以对接Prometheus、Grafana等监控系统。
  • Web管理界面:FE提供Web管理界面,可以查看集群状态、表信息、查询详情、进行管理操作等。
  • 兼容性好:兼容MySQL协议,现有的MySQL客户端、BI工具、ETL工具都能直接用,不需要额外的适配器。

这些特性,让Doris的运维成本比较低,中小团队也能轻松运维。

八、Doris vs ClickHouse vs StarRocks

最后,简单对比一下Doris和其他常见的OLAP数据库,看看它们的区别和适用场景。

1. Doris vs ClickHouse

ClickHouse是俄罗斯Yandex开源的列式存储分析数据库,以极致的查询性能著称,单表查询性能非常强。

Doris和ClickHouse的区别:

  • 架构:Doris是MPP架构,FE+BE,元数据和调度统一管理,分布式表和副本管理简单;ClickHouse是Shared Nothing架构,每个节点独立,分布式表需要自己配置,副本和分片管理相对复杂。
  • 多表join:Doris的多表join性能较好,支持Colocate Join、Runtime Filter等优化,适合复杂的多表关联查询;ClickHouse的多表join性能相对较弱(虽然也在改进),更适合单表或简单join的场景。
  • 实时写入:Doris支持实时写入,写入后立即可查,Compaction机制完善;ClickHouse的写入是批量的,小批量写入性能不好,建议大批量写入,实时写入需要额外处理。
  • 易用性:Doris兼容MySQL协议,运维简单,易用性好;ClickHouse有自己的协议和客户端,虽然也兼容MySQL协议,但兼容性不如Doris,运维相对复杂。
  • 性能:单表查询性能,ClickHouse通常更强(尤其是大宽表聚合);多表join和复杂查询,Doris可能更好。
  • 社区和生态:ClickHouse社区更活跃,生态更丰富,全球用户更多;Doris在国内更火,国内大厂用得多,社区发展很快。

简单来说,如果你主要是单表或简单查询,追求极致性能,选ClickHouse;如果你需要复杂的多表join、实时写入、易用性好,选Doris。

2. Doris vs StarRocks

StarRocks是从Doris分叉出来的一个项目(最初叫DorisDB,后来改名为StarRocks),由一家商业公司主导开发,架构和Doris类似(FE+BE),但做了很多优化和改进。

Doris和StarRocks的区别:

  • 主导方:Doris是Apache基金会项目,社区驱动,中立开放;StarRocks是商业公司主导,有企业版和社区版,商业支持更好。
  • 性能:StarRocks在某些场景下性能更好,比如向量化执行更完善、CBO优化器更强、多表join性能更好,但Doris也在快速追赶,两者差距在缩小。
  • 特性:StarRocks有一些Doris没有的特性,比如主键模型的更新性能更好、支持更丰富的索引、云原生版本等;Doris也有自己的特性,社区生态更开放。
  • 稳定性:Doris发展时间更长,在很多大厂大规模使用,稳定性经过验证;StarRocks相对年轻,但发展很快,稳定性也在不断提升。
  • 商业模式:Doris完全开源免费,没有商业版;StarRocks有社区版(免费)和企业版(收费),企业版有更多特性和支持。

简单来说,如果你想要完全开源、社区中立、经过大规模验证的,选Doris;如果你想要更好的商业支持、某些场景下更好的性能,选StarRocks。两者架构类似,迁移成本不高,可以根据实际需求选择。

九、写在最后

Doris是一个非常优秀的开源OLAP数据库,它的架构简洁高效,存储引擎和查询引擎都有很多优化,性能出色,易用性好,运维简单,非常适合企业级的数据分析场景。

深入研究Doris的原理之后,我对它的设计有了更深的理解,也学到了很多分布式数据库的设计思想。比如列式存储和MPP架构的结合、写入和查询的分离、Compaction机制、向量化执行、CBO优化器、预聚合和物化视图等,这些设计思想不仅适用于Doris,也适用于其他分布式系统。

当然,Doris也不是完美的,它也有一些局限性,比如超高写入量下Compaction可能成为瓶颈、超大规模集群下元数据管理可能有压力、某些复杂查询优化还不够完善等。但Doris社区发展很快,版本更新频繁,这些问题都在不断改进和解决。

总的来说,Doris是一个值得深入研究和使用的OLAP数据库,尤其是在国内的大数据分析场景下,Doris已经成为很多公司的首选。希望本文能帮助大家更好地理解Doris的底层原理,也欢迎大家一起交流讨论。

最后,用一句话结束本文:"理解原理,才能用好工具。"不管用什么数据库,深入理解它的底层原理,才能充分发挥它的性能,避免踩坑。希望大家都能找到适合自己业务场景的数据库,用好它,服务好业务。