Frontend(FE) : 主要负责用户请求的接入、查询解析规划、元数据的存储、节点管理相关工作。
FE又分为 Leader、Follwer和Observer三种角色 ,默认一个Doris集群中只能有一个Leader,可以有多个Follwer和Observer。其中Leader和Follwer组成一个Paxos选择组,如果Leader宕机,则剩下的Follower会自动选出新的Leader,保证单节点宕机情况下元数据的高可用及数据写入高可用。Observer用来扩展查询节点、同步Leader元数据进行备份,如果Doris集群压力非常大,可以扩展 Observer节点来提高集群查询能力,Observer不参与选举、数据写入,只参与数据读取。
Backend(BE) :一个用户请求通过FE解析、规划后,具体的执行计划会发送给BE具体执行, BE主要负责数据存储、查询计划的执行。
BE分布式的存储Doris table表数据,table表数据会经过分区分桶形成tablet,tablet采用列式存储,默认有3个副本。BE会接收FE命令来创建、查询、删除table表,接收来自FE的执行计划并分布式执行。BE会通过索引和谓词下推快速过滤数据,可以在后台执行Compact任务,减少查询时的读放大。
Broker(可选):
Apache Doris架构中除了有BE和FE进程之外,还可以部署Broker可选进程,主要用于支持Doris读写远端存储上的文件和目录。例如:Apache HDFS 、阿里云OSS、亚马逊S3等。
Broker 通过提供一个 RPC 服务端口来提供服务,是一个无状态的 Java 进程,负责为远端存储的读写操作封装一些类 POSIX 的文件操作,如 open,pread,pwrite 等等。
1 支持标准SQL接口
2 列式存储引擎
目前大数据存储有两种方案可以选择,行式存储(Row-Based)和列式存储(Column-Based)。
行式存储在数据写入和修改上具有优势。
列式存储在数据读取和解析、分析数据上具有优势。
3 支持丰富的索引结构
Doris 也支持比较丰富的索引结构,来减少数据的扫描:
Sorted Compound Key Index,可以最多指定三个列组成复合排序键,通过该索引,能够有效进行数据裁剪,从而能够更好支持高并发的报表场景。
Z-order Index :使用 Z-order 索引,可以高效对数据模型中的任意字段组合进行范围查询。
Min/Max :有效过滤数值类型的等值和范围查询。
Bloom Filter :对高基数列的等值过滤裁剪非常有效。
Invert Index :能够对任意字段实现快速检索。
4 支持多种存储模型
在存储模型方面,Doris 支持多种存储模型,针对不同的场景做了针对性的优化:
Aggregate Key 模型:相同 Key 的 Value 列合并,通过提前聚合大幅提升性能。
Unique Key 模型:Key 唯一,相同 Key 的数据覆盖,实现行级别数据更新。
Duplicate Key 模型:明细数据模型,满足事实表的明细存储。
5 支持物化视图
Doris 也支持强一致的物化视图,物化视图的更新和选择都在系统内自动进行,不需要用户手动选择,从而大幅减少了物化视图维护的代价。
6 MPP架构设计
在查询引擎方面,Doris 采用 MPP 的模型,节点间和节点内都并行执行,也支持多个大表的分布式Shuffle Join,从而能够更好应对复杂查询。
7 支持向量化查询引擎
为了实现向量化执行,需要利用CPU的SIMD指令,SIMD的全称是Single Instruction Multiple Data,即用单条指令操作多条数据。现代计算机系统概念中,它是通过数据并行以提高性能的一种实现方式(其他的还有指令级并行和线程级并行),它的原理是在CPU寄存器层面实现数据的并行操作。
Doris 查询引擎是向量化的查询引擎,所有的内存结构能够按照列式布局,能够达到大幅减少虚函数调用、提升 Cache 命中率,高效利用 SIMD 指令的效果。在宽表聚合场景下性能是非向量化引擎的 5-10 倍。
8 动态调整执行计划
Doris 采用了 Adaptive Query Execution 技术, 可以根据 Runtime Statistics 来动态调整执行计划,比如通过 Runtime Filter 技术能够在运行时生成 Filter 推到 Probe 侧,并且能够将 Filter 自动穿透到Probe 侧最底层的 Scan 节点,从而大幅减少 Probe 的数据量,加速 Join 性能。
Doris 的 Runtime Filter 支持In/Min/Max/Bloom Filter。
9 采用CBO和RBO 查询优化器
数据库SQL语句执行流程如下:
在SQL优化器中最重要的一个组件是查询优化器(Query Optimization),在海量数据分析中一条 SQL生成的执行计划搜索空间非常庞大,查询优化器的目的就是对执行计划空间进行裁剪减少搜索空间的代价,查询优化器对于SQL的执行来说非常重要,不管是关系型数据库系统Oracle、MySQL还 是大数据领域中的Hive,SparkSQL, Flink SQL都会有一个查询优化器进行SQL执行计划优化。
有的数据库系统会采用自研的查询优化器,而有的则会采用开源的查询优化器插件,比如Apache Calcite就是一个优秀的开源查询优化器插件。而像Oracle数据库的查询优化器,则是Oracle公司自研的一个核心组件,负责解析SQL,其目的是按照一定的原则来获取目标SQL在当前情形下执行的最高效执行路径。
查询优化器主要解决的是多个连接操作的复杂查询优化,负责生成、制定SQL的执行计划,目前主要有2种查询优化器:基于规则的优化器(RBO)与基于代价的优化器(CBO)
同样,Doris中在优化器方面也是使用 CBO 和 RBO 结合的优化策略,RBO 支持常量折叠、子查询改写、谓词下推等,CBO 支持 Join Reorder。目前 CBO 还在持续优化中,主要集中在更加精准的统计信息收集和推导,更加精准的代价模型预估等方面。
Doris 数据模型上目前分为三类: AGGREGATE KEY, UNIQUE KEY, DUPLICATE KEY,三种模型中数据都是按KEY进行排序。
Aggregate数据模型选择
Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常适合有固定模式的报表类查询场景。但是该模型对 count(*) 查询很不友好。同时因为固定了 Value 列上的聚合方式,在进行其他类型的聚合查询时,需要考虑语意正确性。
Unique数据模型选择
Unique模型针对需要唯一主键约束的场景,可以保证主键唯一性约束。但是无法利用ROLLUP等预聚合带来的查询优势。
对于聚合查询有较高性能需求的用户,推荐使用自1.2版本加入的写时合并实现。
Unique 模型仅支持整行更新,如果用户既需要唯一主键约束,又需要更新部分列(例如将多张源表导入到一张 doris 表的情形),则可以考虑使用 Aggregate 模型,同时将非主键列的聚合类型设置为REPLACE_IF_NOT_NULL。
Duplicate数据模型选择
Duplicate 这种数据模型适用于既没有聚合需求,又没有主键唯一性约束的原始数据的存储,适合任意维度的 Ad-hoc 查询。虽然同样无法利用预聚合的特性,但是不受聚合模型的约束,可以发挥列存模型的优势(只读取相关列,而不需要读取所有 Key 列)。
在定义Doris表中列类型时有如下建议:
Doris 支持两层的数据划分:
第一层是 Partition,即分区。用户可以指定某一维度列作为分区列,并指定每个分区的取值范围, 分区支持 Range 和 List 的划分方式。
第二层是 Bucket分桶(Tablet),仅支持 Hash 的划分方式,用户可以指定一个或多个维度列以及桶数对数据进行 HASH 分布或者不指定分桶列设置成 Random Distribution对数据进行随机分布。
创建Doris表时也可以仅使用一层分区,使用一层分区时,只支持Bucket分桶划分,这种表叫做单分区表;如果一张表既有分区又有分桶,这张表叫做复合分区表。
分区用于将数据划分成不同区间, 逻辑上可以理解为将原始表划分成了多个部分。可以方便的按分区对数据进行管理,例如,删除数据时,更加迅速。Partition支持Range和List的划分方式。
使用分区时注意点如下:
1. Partition 列可以指定一列或多列,分区列必须为 KEY 列。
2. 不论分区列是什么类型,在写分区值时,都需要加双引号。
3. 分区数量理论上没有上限。
4. 当不使用 Partition 建表时,系统会自动生成一个和表名同名的,全值范围的 Partition。该 Partition 对用户不可见,并且不可删改。
5. 创建分区时不可添加范围重叠的分区。
业务上,多数用户会选择采用按时间进行partition。Range分区列通常为时间列,以方便管理新旧数据。
创建Range分区方式
Partition支持通过"VALUES [...)"指定下界,生成一个左闭右开的区间。也支持通过" VALUES LESS THAN (...)"仅指定上界,系统会将前一个分区的上界作为该分区的下界,生成一个左闭右开的区。从 Doris1.2.0版本后也支持通过"FROM(...) TO (...) INTERVAL ..."来批量创建分区。
多列分区
Range分区除了上述我们看到的单列分区,也支持多列分区。
业务上,用户可以选择城市或者其他枚举值进行partition,对于这种枚举类型数据列进行分区就可以使用List分区。List分区列支持 BOOLEAN, TINYINT, SMALLINT, INT, BIGINT, LARGEINT, DATE, DATETIME, CHAR, VARCHAR 数据类型, 分区值为枚举值。只有当数据为目标分区枚举值 其中之一时,才可以命中分区。
创建List分区方式
Partition 支持通过 VALUES IN (...) 来指定每个分区包含的枚举值。
多列分区
List分区也支持多列分区。
Doris数据表存储中,如果有分区,在插入数据时,数据会按照对应规则匹配写往对应的分区中,如果表除了有分区还有分桶,那么数据在写入某个分区后,还会根据分桶规则将数据写入不同的分桶 (Tablet),目前分桶Bucekt目前仅支持Hash分桶,即根据对应列的hash值将数据划分成不同的分桶(Tablet)。
建议采用区分度大的列做分桶, 避免出现数据倾斜,为方便数据恢复, 建议单个 bucket 的 size 不要太大, 保持在 10GB 以内, 所以建表或增加 partition 时请合理考虑 bucket 数目, 其中不同 partition可指定不同的 buckets数。
使用Bucket分桶有以下几个注意点:
1. 如果使用了 Partition,则 DISTRIBUTED ... 语句描述的是数据在各个分区内的划分规则。如果不使用 Partition,则描述的是对整个表的数据的划分规则。
2. 分桶列可以是多列, Aggregate 和 Unique 模型必须为 Key 列,Duplicate 模型可以是 key 列和 value 列。分桶列可以和 Partition 列相同或不同。
3. 分桶列的选择,是在"查询吞吐" 和 "查询并发" 之间的一种权衡:
如果选择多个分桶列,则数据分布更均匀。如果一个查询条件不包含所有分桶列的等值条件,那么该查询会触发所有分桶同时扫描,这样查询的吞吐会增加,单个查询的延迟随之降低。这个方式适合大吞吐低并发的查询场景。如果仅选择一个或少数分桶列,则对应的点查询可以仅触发一个分桶扫描。此时,当多个点查询并发时,这些查询有较大的概率分别触发不同的分桶扫描,各个查询之间的IO影响较小(尤其当不同桶分布在不同磁盘上时), 所以这种方式适合高并发的点查询场景。
4. 分桶的数量理论上没有上限。
1. 一个表必须指定分桶列,但可以不指定分区
2. 对于分区表,可以在之后的使用过程中对分区进行增删操作,而对于无分区的表,之后不能再进行增加分区等操作。
3. 分区列和分桶列在表创建之后不可更改,既不能更改分区和分桶列的类型,也不能对这些列进行任何增删操作。所以建议在建表前,先确认使用方式来进行合理的建表。
4. 一个表的 Tablet 总数量等于 (Partition num * Bucket num)。
5. 一个表的 Tablet 数量,在不考虑扩容的情况下,推荐略多于整个集群的磁盘数量。
6. 单个 Tablet 的数据量理论上没有上下界,但建议在 1G - 10G 的范围内。如果单个 Tablet 数据量过小,则数据的聚合效果不佳,且元数据管理压力大。如果数据量过大,则不利于副本的迁移、补齐,且会增加 Schema Change 或者 Rollup 操作失败重试的代价(这些操作失败重试的粒度是 Tablet)。
7. 当 Tablet 的数据量原则和数量原则冲突时,建议优先考虑数据量原则。
8. 在建表时,每个分区的 Bucket 数量统一指定。但是在动态增加分区时(ADD PARTITION),可以单独指定新分区的 Bucket 数量。可以利用这个功能方便的应对数据缩小或膨胀。
9. 一个 Partition 的 Bucket 数量一旦指定,不可更改。所以在确定 Bucket 数量时,需要预先考虑集群扩容的情况。比如当前只有 3 台 host,每台 host 有 1 块盘。如果 Bucket 的数量只设置为 3 或更小,那么后期即使再增加机器,也不能提高并发度。
以下场景推荐使用复合分区:
1. 有时间维度或类似带有有序值的维度,可以以这类维度列作为分区列。 分区粒度可以根据导入频次、分区数据量等进行评估。
2. 历史数据删除需求: 如有删除历史数据的需求(比如仅保留最近N 天的数据)。使用复合分区,可以通过删除历史分区来达到目的。也可以通过在指定分区内发送 DELETE 语句进行数据删除。
3. 解决数据倾斜问题: 每个分区可以单独指定分桶数量。如按天分区,当每天的数据量差异很大时,可以通过指定分区的分桶数,合理划分不同分区的数据,分桶列建议选择区分度大的列。当然用户也可以不使用复合分区,即使用单分区,则数据只做 HASH 分布。
Doris 提供多种数据导入方案,可以针对不同的数据源进行选择不同的数据导入方式。Doris支持各种各样的数据导入方式:Insert Into、json格式数据导入、Binlog Load、Broker Load、Routine Load、Spark Load、Stream Load、S3 Load,下面分别进行介绍。
注意:Doris 中的所有导入操作都有原子性保证,即一个导入作业中的数据要么全部成功,要么全部失败,不会出现仅部分数据导入成功的情况。
Doris Exeport、Select Into Outfile、MySQL dump三种方式数据导出。用户可以根据自己的需求导出数据。此外数据还可以以文件形式通过Borker备份到远端存储系统中,之后可以通过恢复命令来回复到Doris集群中。
Doris 支持BACKUP方式将当前数据以文件的形式,通过 broker 备份到远端存储系统中。之后可以通过 RESOTRE命令进行恢复,从远端存储系统中将数据恢复到任意 Doris 集群。通过这个功能,Doris 可以支持将数据定期的进行快照备份。也可以通过这个功能,在不同集群间进行数据迁移。
Doris为了避免误操作造成的灾难,支持对误删除的数据库/表/分区进行数据恢复,在drop table或者 drop database之后,Doris不会立刻对数据进行物理删除,而是在 Trash 中保留一段时间(默认 1天,可通过fe.conf中catalog_trash_expire_second参数配置),管理员可以通过RECOVER命令对误删除的数据进行恢复。
Update 数据更新只能在 Unique 数据模型的表中执行,使用场景为:对满足某些条件的行进行修改值或小范围数据更新,待更新的行最好是整个表非常小的一部分。
Doris 支持通过两种方式对已导入的数据进行删除。
一种是通过 DELETE FROM 语句,指定 WHERE 条件对数据进行删除。这种方式比较通用, 适合频率较低的定时删除任务。
另一种删除方式仅针对 Unique 主键唯一模型, 通过导入数据的方式将需要删除的主键行数据进行导入。 Doris 内部会通过删除标记位对数据进行最终的物理删除。 这种删除方式适合以实时的方式对数据进行删除。
Doris支持了sequence列,通过用户在导入时指定sequence列,相同key列下,REPLACE聚合类型的列将按照sequence列的值进行替换,较大值可以替换较小值,反之则无法替换。该方法将顺序的确定交给了用户,由用户控制替换顺序。
sequence 列目前只支持 Unique 存储模型。
批量删除只针对 Unique 模型的存储表。
目前Doris 支持 Broker Load,Routine Load, Stream Load 等多种导入方式,针对一张已经存在的Unique表,通过不同的导入方式向表中增加数据时,导入的数据有三种合并方式:
APPEND: 数据全部追加到现有数据中【默认】;
DELETE: 删除所有与导入数据key 列值相同的行(当表存在sequence列时,需要同时满足主键相同以及sequence列的大小逻辑才能正确删除);
MERGE: 根据 DELETE ON 的决定 APPEND 还是 DELETE。
需要注意的是动态分区只支持Range 分区。
动态分区的规则可以在建表时指定,或者在运行时进行修改。 当前仅支持对单分区列的分区表设定动态分区规则。
物化视图是将预先计算(根据定义好的 SELECT 语句)好的数据集,存储在 Doris 中的一个特殊的表。物化视图的出现主要是为了满足用户,既能对原始明细数据的任意维度分析,也能快速的对固定维度进行分析查询。
物化视图使用场景及优势
Doris 物化视图使用的场景如下 :
分析需求经常对Doris某个明细表或者固定维度进行查询。查询仅涉及表中一小部分行或者列的聚合查询。
查询包含一些耗时处理操作,比如:时间很久的聚合操作等。
查询需要匹配不同前缀索引。
Doris 物化视图的优势如下 :
对于那些经常重复的使用相同的子查询结果的查询性能大幅提升。
Doris自动维护物化视图的数据,无论是新的导入,还是删除操作都能保证base 表和物化视图表的数据一致性。无需任何额外的人工维护成本。
查询时,会自动匹配到最优物化视图,并直接从物化视图中读取数据。
物化视图&Rollup对比
在没有物化视图功能之前,用户一般都是使用 Rollup 功能通过预聚合方式提升查询效率的。但是 Rollup 具有一定的局限性,他不能基于明细模型做预聚合。
物化视图则在覆盖了 Rollup 的功能的同时,还能支持更丰富的聚合函数。所以物化视图其实是Rollup 的一个超集。也就是说,之前 ALTER TABLE ADD ROLLUP 语法支持的功能现在均可以通过 CREATE MATERIALIZED VIEW 实现。
物化视图语法
Doris 系统提供了一整套对物化视图的 DDL 语法,包括创建,查看,删除。DDL 的语法和 PostgreSQL, Oracle都是一致的。
物化视图局限性
1. 物化视图的聚合函数的参数不支持表达式仅支持单列,比如: sum(a+b)不支持。(2.0后支持)
2. 如果删除语句的条件列,在物化视图中不存在,则不能进行删除操作。如果一定要删除数据,则需要先将物化视图删除,然后方可删除数据。
3. 单表上过多的物化视图会影响导入的效率:导入数据时,物化视图和 base 表数据是同步更新的,如果一张表的物化视图表超过10张,则有可能导致导入速度很慢。这就像单次导入需要同时导入10张表数据是一样的。
4. 相同列,不同聚合函数,不能同时出现在一张物化视图中,比如:select sum(a), min(a) from table 不支持。(2.0后支持)
5. 物化视图针对 Unique Key数据模型,只能改变列顺序,不能起到聚合的作用,所以在Unique Key模型上不能通过创建物化视图的方式对数据进行粗粒度聚合操作。
6. 物化视图目前仅支持针对某张物理表创建,不支持join,不支持语句中有where子句。大概原因是为了保证与源表数据保持一致,所以不支持where语句,因为物化视图中会有一些聚合情况。