Doris知识点总结2

FE和BE的作用分别是什么?

存储方面:

(1)FE(Frontend)负责存储,维护集群的元数据信息。

  • FE的主要职责是存储和管理四类数据:
  • 用户数据信息:包括数据库,表的Schema和分片信息等。
  • 各类作业信息:例如导入作业、克隆作业和SchemaChange作业等。
  • 用户和权限信息。
  • 集群和节点信息。

(2)BE(Backend)负责存储物理数据。

  • Doris使用最小的逻辑单元为tablet,最小的物理单元为rowset。
  • BE负责将数据存储在内存中,并按照预定义的格式刷写到物理磁盘上。
  • 每次提交导入任务后,会在物理磁盘上形成一个小的rowset,而Doris会使用异步的compaction任务来合并这些rowset,形成较大的rowset,并使用base compaction任务将它们合并为段(segment)。
  • 在读取数据时,最终会扫描这些segment对象来获取数据。

查询方面:

(1)FE负责接收、解析查询请求,规划查询计划,调度查询执行,并返回查询结果。

  • FE接受用户的查询请求,进行词法解析,语法解析和语义解析。
  • 根据解析结果,生成逻辑执行计划和物理执行计划。
  • FE根据物理执行计划将查询任务分发给对应的BE节点上执行,并协调各个BE节点之间的数据交换和计算。
  • FE收集BE节点返回的查询结果,并返回给用户。

(2)BE负责依据FE生成的物理查询计划,分布式地执行查询计划。

  • BE根据FE生成的物理查询计划,分布式地执行查询计划。
  • BE节点根据查询计划并行地执行具体的查询操作,包括数据扫描,过滤,聚合和排序等。
  • BE节点之间可能需要进行数据交换和合并,以完成复杂的查询操作。
  • BE节点将局部的查询结果返回给FE节点,供FE节点汇总并返回给用户。

Doris的FE节点的选举机制

 Doris中采用Raft算法实现FE节点的选举机制。

在Raft算法中,集群中的每个节点都有三种状态:Leader、Follower、Candidate。Leader节点负责接受来自客户端的请求以及向Followers节点同步数据,并维护全局日志。Follower节点则根据Leader的指示执行请求操作,并将结果返回给客户端。Candidate节点则是通过选举机制产生的中间状态,当某个时刻没有Leader节点存在时,便会让所有的Candidate节点发起投票,选出新的Leader节点。

1)初始阶段:

当第一个FE节点启动时,它会将自己作为候选人Candidate,并向其他节点发送投票请求。因此此时集群内还没有Leader节点,其余节点都会接受投票请求,将自己的任期号更新为当前候选人的任期号,并为其投一票。由于候选人得到了超过集群半数以上的支持,因此该候选人当选为新的Leader节点,并开始协调客户端请求和管理集群状态。

2)正常运行阶段:

当集群中已经有一个Leader存在时,如果该节点失败(例如宕机、网络故障等),其他FE节点会检测到Leader的失败,并开始进行新一轮的选举流程。在这个情况下,Doris的选举过程分为两个阶段:Candidate阶段和Leader阶段。

  • Candidate阶段:当一个节点发现当前没有Leader节点时,就会进入Candidate阶段,并向其他所有节点广播投票请求以获得支持。在此阶段,每个Candidate节点都会提议自己成为新的Leader节点,并广播请求给其他节点,同时增加自己的term值。其他节点收到请求后,会比对请求中的term值,如果请求中的term值比自己的小,就拒绝该投票请求。否则,将更新自己本地的term值,并投票支持该Candidate节点。如果一个Candidate节点获得超过大多数节点的支持,那么它就进入了Leader阶段。
  • Leader阶段:在进入Leader阶段之前,当选的Candidate节点需要维护一个递增的commitIndex值,作为最后已知的已提交日志条目的索引。在广播成为Leader节点之后,它需要发送心跳包以维持其领导地位并通知其他节点与自己同步。每个Follower节点需要回复心跳响应,以便证明它们仍然和新的Leader保持联系。如果Leader节点失效或者宕机了,整个选举过程将重新开始。

Doris数据导入的compaction机制

     Doris的存储引擎通过类似LSM的数据结构提供快速的数据导入支持,对于单一的数据分片(Tablet),新的数据先写入内存结构,随后刷入磁盘,形成一个个不变更的数据文件,这些数据文件保存在一个个rowset中,而Doris的Compaction机制主要负责根据一定的策略对这些rowset进行合并,将小文件合并成大文件,进而提升查询性能。

     每一个rowset都对应一个版本信息,表示当前rowset的版本范围,版本信息中包含了两个字段first和second,first表示当前rowset的起始版本(start version),second表示当前rowset的结束版本(end version)。每一次数据导入都会生成一个新的数据版本,保存在一个rowset中。未发生过compaction的rowset的版本信息中first字段和second字段相等。执行compaction时,相邻的多个rowset会进行合并,生成一个版本范围更大的rowset,合并生成的rowset版本信息回包含合并前的所有rowset的版本信息。

  compaction分为两种类型:base compaction和cumulative compaction。其中cumulative compaction则主要负责将多个最新导入的rowset合并成较大的rowset,而base compaction会将cumulative compaction产生的rowset合并到start version为0的基线数据版本(Base Rowset)中,故是一种开销比较大的compaction操作。这两种compaction的边界通过cumulative point来确定。base compaction会将cumulative point之前的所有rowset进行合并,cumulative compaction会在cumulative point之后选择相邻的数个rowset进行合并。

Doris数据导入流程梳理

1)Stream Load

 本质上是一个HTTP的PUT请求 ,执行流程如下:

流程图

流程详解

(1)用户发起导入请求,该请求可以直接发往FE,由FE将请求转发给某个BE,由该BE充当协调者的角色,也可以由用户自己在导入请求时指定某个BE为协调者角色,发起导入请求。

   ps:如果把导入作业请求发送给 FE,FE 会通过轮询机制选定由哪一个 BE 来接收请求,从而实现StarRocks集群内的负载均衡,推荐导入作业的请求直接发送给 FE。

(2)协调者在收到导入请求后,会把数据分发到其他BE数据节点,当集群内一份数据有两个数据节点完成数据写入后,就标志着这次导入事务成功,剩余的一份数据由剩下的节点从这两个副本中区同步数据。

(3)导入数据成功后,协调者会将导入任务的状态返回给用户。

注意事项

(1)请求直接发往FE时,FE 会通过 HTTP 重定向 (Redirect) 指令将请求转发给某一个 BE。需要注意重定向过程中,可能会由于网络波动问题造成导入任务的失败。

(2) 建议一次导入的数据量不要超过10GB,否则失败重试的代价过大。源文件较大,可以拆分为多个Stream Load任务并行的方式(手动指定不同的协调者BE)来提高Stream Load导入任务的导入性能。如果确实无法拆分,可以适当调大该参数的取值,从而提高数据文件的大小上限。

   ps :如果调大该参数的取值,需要重启 BE 才能生效,系统性能可能会受影响,失败重试时的代价也会增加。

    Stream Load导入作业的系统参数配置:streaming_load_max_mb代表单个源数据文件的大小上限,默认文件大小上限为10GB

2)Broker Load

 核心利用Broke进程拉取远端存储的数据,同BE做相关的数据交互。

流程图

流程详解
(1)用户在客户端创建broker load任务。
(2)FE在接收到用户的创建请求后,会根据请求导入的源文件的数据量和文件数量以及BE的数量生成plan,并将plan分发到多个be节点上,每个BE会负责一定数据量的导入任务。
(3)对应的BE在接收到导入任务时,会通过broker进程,去远端存储系统上拉去相应的数据到对应的BE上,在对数据 transform之后将数据导入StarRocks系统。
(4)所有be均完成导入,由FE最终决定导入是否成功,并返回最终结果给用户;

 注意事项
(1)最好是每个BE节点上创建一个broker进程,同时broker进程的名称保持一致,用户在发起导入任务的时候,可以尽可能的保证所有的broker进程和对应的BE节点参与到导入任务中来,最大化的提高导入性能。

(2)源文件不建议是数量较多的碎片化小文件,同时也不建议是数据量比较大的数量较少的文件个数,可以酌情把小文件合并或者大文件拆分成数量为be倍数的个数,单个文件大概在几十到百GB级别大小的文件。

3)Routine Load

  Routine Load(例行导入)支持用户提交一个常驻的导入任务,可以将消息流存储在kafka的Topic中,通过订阅Topic 中的全部或部分分区的消息,从而实现不间断的导入至于StarRocks。(Routine Load任务是常驻进程)。  Routine Load支持 Exactly-Once 语义,能够保证数据不丢不重。支持从kafka集群中消费CSV,JSON,Avro (自 v3.0.1) 格式的数据。 总结:通过在数据库侧建立常驻消费者进程来拉取位于流系统上的数据,该消费者进程会按照定义好的消费逻辑和间隔,攒批数据之后调用Stream Load导入机制来实现数据导入。

流程图

流程详解

(1)用户通过客户端发起创建routine load任务的请求。

(2)FE在收到请求后,会将请求任务通过Job Scheduler将任务拆分成若干个Task,每个Task被TaskScheduler分配到指定的BE上执行。

(3)拆分的每个task会被Task Scheduler调度到不同的BE节点上执行。

 即: 多个导入任务并行进行,消费kafka多个分区的数据导入至StarRocks

 (4)位于每个BE节点的Task 任务会按照预先定义好的消费逻辑,数据攒批后调用stream load任务,完成对应批次数据的导入。

(5)持续生成新的导入任务,不间断地导入数据。

        Executor BE节点成功写入数据后, Coordonator BE会向FE汇报导入结果。FE根据汇报结果,继续生成新的导入任务,或者对失败的导入任务进行重试,连续地导入数据,并且能够保证导入数据不丢不重。

   总结:可以将上述的执行流程理解为一个个不断调度执行的Stream Load任务。在默认参数下,一个Stream Load任务被拆分成若干个Task,Task被调度后,开始对kafka进行为期15秒①的数据消费,并现在内存中攒批15秒过后这批数据通过Stream Load的方式导入到对应数据表中,任务完成后向FE汇报,然后间隔10秒后②,Task被再次调度,如果循环进行。从Task被调度到本次Stream Load任务完成,整个过程的超时时间默认限制是60秒③。FE收到任务汇报结果后,会继续生成后续新的Task,或者对失败的Task进行重试(最多重试3次,都失败则任务暂停)。整个Stream Load作业通过不断的产生新的Task,来完成数据不间断的导入。上述提到的三个时间参数可以结合实际的业务情况来修改。

注意事项

(1)Routine Load本质上还是调用的Stream load任务,需要注意攒批频次的设置,不能太过于频繁的去调用,避免未合并的版本数超限(compaction合并问题)。

(2)Routine Load任务的消费频次:根据消息的峰值变化速率来设定不同任务的消费频次。

(3)过多的Routine Load任务会占用一定的硬件资源,会导致查询性能的下降。(Routine Load任务是常驻进程

Doris的高并发点查是如何实现的?

   对于高并发查询,其核心在于如何平衡有限的系统资源消耗与并发执行带来的高负载。换而言之,需要最大化降低单个SQL执行时的CPU,内存和IO开销,其关键在于减少底层数据的Scan以及随后的数据计算。Doris能够实现高并发查询的能力主要是通过以下几个方面:

 1)MPP架构

    基于大规模并行处理(Massively Parallel Processing, MPP)架构设计,它可以将查询分解为多个任务,在多个节点上并行执行这些任务,使得系统可以通过增加更多的计算资源来线性扩展其查询处理能力。

2)列式存储

   使用列式存储格式,对于任务给定的查询,它只需要读取涉及到的列而不是整行数据。这就减少了磁盘I/O压力,因为只有必需的数据被加载到内存中。

3)分区分桶裁剪

     Doris 采用两级分区,第一级是 Partition,通常可以将时间作为分区键。第二级为 Bucket,通过 Hash将数据打散至各个节点中,以此提升读取并行度并进一步提高读取吞吐。通过合理地划分区分桶,可以提高查询性。

4)索引和物化视图

      Doris 支持创建索引和物化视图来加速查询,减少扫描行数和避免了大量的现场计算,例如前缀索引、Ordinal、ZoneMap、Bitmap和Bloom filter等索引和预计算物化。

索引

     Doris 提供了丰富的索引结构来加速数据的读取和过滤。包括内置的前缀索引(Short Key Index)、Ordinal 索引、ZoneMap索引以及用户手动创建的Bitmap索引和 Bloom filter索引。其中内置索引是在 Doris 数据写入时自动生成的,无需用户干预。

  • 前缀稀疏索引(Short Key Index)

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

  • Ordinal Index
    Ordinal lndex索引提供了通过行号来查找Column Data Page数据页的物理地址,Ordinal lndex索引能够将按照列存储的数据按行对齐,可以理解为一级索引。
    在一个segment文件中,数据始终按照key排序存储,数据写入的过程中,每一列的data page会由Ordinal Index管理,他会记录每一列对应的data page的offset,size大小,和该data page的第一个数据的行号信息。这样在查询的时候,就能通过 Ordinal lndex索引够快速定位到对应的data page的物理地址。
  • ZoneMap 索引

     Doris会为Segment文件中的一列数据(key 列)自动添加ZoneMap索引,注意:当表的模型为dupulcate时,会所有字段开启zonemap索引。
   ZoneMap索引存储了Segment和每个列对应每个Page的统计信息。Doris可以根据这些统计信息,快速判断这些数据块是否可以过滤掉,从而减少扫描数据量,提升查询速度。统计信息包括了Min最大值、Max最小值、HashNull空值、HasNotNull不全为空的信息。
  • BitMap索引

     Doris支持对低基数列创建Bitmap位图索引来加速数据查询。高基数列:例如UserID,低基数列:例如性别,婚姻状态等。

    Bitmap位图索引创建时需要通过  create index 进行创建。Bitmap的索引是整个Segment中的Column字段的索引,而不是为每个Page单独生成一份。在写入数据时,会维护一个map结构,去记录下每个key值对应的行号,并采用Roaring位图对rowid进行编码。生成索引数据时,首先写入字典数据,即将map结构的key值写入到DictColumn中。然后,key对应Roaring编码的rowid(value值)以字节方式将数据写入到BitMapColumn。  

  • BloomFilter 索引

    Doris支持用户对适用于高基数列(取值区分度比较大的字段)添加Bloom Filter(布隆过滤器)索引,Bloom filter索引主要用于快速判断某列中是否存在某个值。BloomFilter判定该列中不存在指定的值,如果确定不存在,就不会读取这个数据文件;如果索引判定该列中存在指定的值,也有可能这个值实际上不会存在,这时,会读取数据文件来进一步确认。

  ps:高基数列:例如UserID,低基数列:例如性别,婚姻状态等。

    以查询语句为例:select * from user_table where id > 10 and id < 1024;

   假设按照 ID 作为建表时指定的 Key, 那么在 Memtable 以及磁盘上按照 ID 有序的方式进行组织,查询时如果过滤条件包含前缀字段时,则可以使用前缀索引快速过滤。Key 查询条件在存储层会被划分为多个 Range,按照前缀索引做二分查找获取到对应的行号范围,由于前缀索引是稀疏的,所以只能大致定位出行的范围。随后过一遍 ZoneMap、Bloom Filter、Bitmap 等索引,进一步缩小需要 Scan 的行数。通过索引,大大减少了需要扫描的行数,减少 CPU 和 IO 的压力,整体大幅提升了系统的并发能力。

物化视图

    物化视图是一种典型的空间换时间的思路,其本质是根据预定义的SQL 分析语句执⾏预计算,并将计算结果持久化到另一张对用户透明但有实际存储的表中。在需要同时查询聚合数据和明细数据以及匹配不同前缀索引的场景,命中物化视图时可以获得更快的查询相应,同时也避免了大量的现场计算,因此可以提高性能表现并降低资源消耗。

5)向量化查询执行

    Doris实现了向量化查询处理,在执行操作时它可以一次处理数据列的一整块,而不是逐行处理。这样可以大大提高CPU的利用率,降低每个数据点的处理开销。

6)Runtime Filter

     Doris 中额外加入了动态过滤机制,即 Runtime Filter。在多表关联查询时,通常将右表称为 BuildTable、左表称为 ProbeTable,左表的数据量会大于右表的数据。在实现上,会首先读取右表的数据,在内存中构建一个 HashTable(Build)。之后开始读取左表的每一行数据,并在 HashTable 中进行连接匹配,来返回符合连接条件的数据(Probe)。而 Runtime Filter 是在右表构建 HashTable 的同时,为连接列生成一个过滤结构,可以是 Min/Max、IN 等过滤条件。之后把这个过滤列结构下推给左表。这样一来,左表就可以利用这个过滤结构,对数据进行过滤,从而减少 Probe 节点(左表)需要传输和比对的数据量。在大多数 Join 场景中,Runtime Filter 可以实现节点的自动穿透,将 Filter 穿透下推到最底层的扫描节点或者分布式 Shuffle Join 中。大多数的关联查询 Runtime Filter 都可以起到大幅减少数据读取的效果,从而加速整个查询的速度。

  总结,通过以上一系列优化手段,可以将不必要的数据剪枝掉,减少读取、排序的数据量,显著降低系统 IO、CPU 以及内存资源消耗,从而提升并发。

   上文提到的几点优化技术,Doris 实现了单节点上千QPS 的并发支持。但在一些超高并发要求(例如数万 QPS)的 Data Serving 场景中,仍然存在瓶颈:

  • 列式存储引擎对于行级数据的读取不友好,宽表模型上列存格式将放大随机读取 IO;
  • OLAP 数据库的执行引擎和查询优化器对于某些简单的查询(如点查询)来说太重,需要在查询规划中规划短路径来处理此类查询;
  • SQL 请求的接入以及查询 计划的解析与生成由 FE 模块负责,使用的是 Java 语言,在高并发场景下解析和生成大量的查询执行计划会导致高 CPU 开销;

Doris-connector 数据一致性的实现

   数据一致性三种:at-most-once; at-least-once; exactly-once。flink提供checkpoint的机制来实现exactly-once,checkpoint机制会为对应流计算过程中的所有的算子制作状态快照,保存在持久化存储中。当任务失败时,flink便会从对应的快照信息中恢复最近一次的完整信息,从而保证数据被精准一次消费。而Doris flink connector的精准一次依赖于2pc的机制来实现的。具体流程如下:

  •  首先Flink任务启动后便会发起一个stream load的parper请求,此时会先开启一个事务,同时通过http的chunked机制往doris中写入数据。
  • 在checkpoint时,结束数据的写入,同时完成http的请求,事务状态会变更为precommitted,此时数据已经写入BE,但对用户不可见。
  • checkpnint完成后,发起commit请求并且更改事务状态为commited,完成后数据对用户可见
  • 如果过程中Flink任务意外怪掉,从checkpoint重启时,若上次的事务状态为precommitted状态,则会触发回滚请求,并且设置事务状态为aborted  

Doris的数据分布

    Doris的数据分布是在建表的时候就需要预先定义的,数据分布的好坏与查询性能强相关。Doris支持两级分区存储,第一层为range 分区(partition),第二层为hash 分桶(bucket)。

  • 表,分区,分桶以及副本的关系:

    Table (逻辑描述) -- > Partition(分区:管理单元) --> Bucket(分桶:每个分桶就是一个数据分片:Tablet,数据划分的最小逻辑单元)

   以一个具体的案例说明:

     分区是逻辑概念,分桶是物理概念。每个分区partition内部会按照分桶键,采用哈希分桶算法将数据划分为多个桶bucket,每个桶的数据称之为一个数据分片tablet(实际的物理存储单元)。根据建表设置的副本数,计算有多少个副本在其他节点上(负载均衡)。如上图所示,当BE节点数和副本数一致时,每个BE节点会保留这个表对应的所有的tablet的数据(自身hash分桶对应的tablet数据和其他节点tablet的副本)。

    一个表的Tablet总数量等于 = Partition num * Bucket num* Replica Num ,上述案例就等于 3*3*3 =27 

  • 表,分区,分桶,tablet,rowset,segment文件的关系:

    数据在导入时,最先落到磁盘上的是当前导入批次生成的rowset文件,每一次导入事务的成功,都会生成一个带有版本号的rowset文件。rowset文件由多个segment文件组成,segment文件是由segment writer生成的文件块。

   合并生成的rowset文件会有一个带有起始index的version号,没有经过合并的rowset文件的起始索引号时一样的。然后根据相应的合并策略,满足这些合并策略的rowset文件会根据version号再次进行合并,生成tablet文件供查询使用。

    基于上面数据导入生成tablet文件的原理,生产环境需要避免高频次小数据量的导入,否则compaction任务的压力会比较大,造成未合并的版本数过多,超出1000的限制,导致后续的数据导入无法正常进行。

Doris可以兼任业务生产数据库角色嘛?即事务型 OLTP库

     在大部分TP场景下,Doris是满足不了的,因为TP主打事务和毫秒插入频次,高频次的INSERT操作会导致在存储层产生大量的小文件,会严重影响系统性能。对于AP库而言,这种操作是非常吃亏的,这里主要是因为AP库在本身设计的阶段,就是采用了数据版本做数据可见性控制,一批数据导入之后,可以对数据批次标记版本号,在默认的MOR(merge-on-read 读时合并)的策略下,根据高版本号进行展示。

    并且Doris的数据导入是是无形的,如果是INSERT INTO VALUES 模式,很容易引起版本堆积,大量的数据版本会导致后台自动运行的Compaction进程压力增大,导致整个集群负载变高,所以从此角度而言,Doris是无法兼任TP库的。

    但是在一些延迟非敏的场景下,比如前端数据经过后端处理后,结果数据需在毫秒级响应,此时Doris是可以胜任的,Doris 可以将数据插入批次最小压缩到 0.5-1s 左右,再利用 Unique 模型的 Replace 算子和 Agg 模型的 Replace_if_not_null 算子进行更新,即可完成 TP 任务。

Doris SQL解析流程

详情链接:Doris ——SQL原理解析_sql物理计划生成代码-CSDN博客

   Doris SQL解析是指代一条SQL语句经过一系列的解析后,生成一个完整的物理执行计划的过程。其包括词法分析、语法分析、生成逻辑计划和生成物理计划这四个步骤。

(1)词法分析:主要负责将字符串形式的sql识别成一个个token,为语法分析做准备。例如:

select ......  from ...... where ....... group by ..... order by ......
SQL 的 Token 可以分为如下几类:
○ 关键字(select、from、where)
○ 操作符(+、-、>=)
○ 开闭合标志((、CASE)
○ 占位符(?)
○ 注释
○ 空格
...

(2)语法分析:主要负责根据语法规则,将词法分析生成的token转成抽象语法树(AST),例如:

(3)逻辑查询计划:负责将抽象语法树转成代数关系。代数关系是一棵算子树,每个节点代表一种对数据的计算方式,整棵树代表了数据的计算方式以及流动方向。例如:

  • 单机逻辑查询计划:对AST经过一系列优化(比如:谓词下推等)成查询计划,提高执行性能和效率
  • 分布式逻辑查询计划:根据分布式环境(数据分布信息、连接信息、join算法等)将单机逻辑查询计划转换成分布式

(4)物理查询计划:在逻辑查询计划的基础上,根据数据的存储方式和机器的分布情况生成实际的执行计划。

 详细总结如下:

Doris的Pipeline执行引擎解决了什么问题

现存痛点:

  1. 多核计算能力的利用问题:传统的火山模型在单机多核的场景下无法充分利用多核计算能力,导致查询性能无法得到提升。这是因为传统模型多数情况下需要手动设置并行度,而在生产环境中很难进行适当的设定。
  2. 线程池和死锁问题:Doris的查询引擎采用线程池来处理查询,每个查询实例对应线程池的一个线程。然而,当线程池被占满时,Doris的查询引擎会进入假死状态,无法响应后续的查询请求。此外,还存在逻辑死锁的情况,例如所有线程都在执行一个实例的任务时。这些问题导致了查询引擎的可用性问题。
  3. 阻塞操作和线程切花开销:阻塞算子在传统模型中会占用线程资源,并且无法将阻塞的线程资源让渡给其他可调度的实例。这导致整体资源利用率不高。此外,阻塞算子还依赖操作系统的线程调度机制,而线程切换开销较大,尤其在系统混布的场景中。这增加了执行的开销并降低查询性能。

   为了解决上述问题,Doris引入了充分释放多核CPU计算能力的Pipeline执行引擎。以下是该引擎的关键特点:

  1. 数据驱动的Push模型:相比传统的Pull模型,Pipeline执行引擎通过数据驱动的Push模型重新设计了执行流程。意味着数据会主动推送到下一个算子,而不需要等待被拉取,这种改进可以减少等待时间,提高查询的执行效率。
  2. 异步化阻塞操作:Pipeline执行引擎将阻塞操作异步化处理,意味着当一个算子执行阻塞操作时,它不会阻塞整个线程,而是让其他可执行的任务继续执行。这样可以减少线程切换和阻塞带来的执行开销,提高CPU的利用效率。
  3. 控制执行线程数目:Pipeline执行引擎通过控制执行线程的数量,在混合负载的场景中减少大查询对小查询资源的抢占情况。通过时间片的切换控制,可以平衡不同查询之间的资源分配,提高整体系统的性能。

Doris等OLAP引擎的对比

    TiDB更倾向于满足 TP (事务,点查场景)需求,在应对大数据量分析场景时性能相对不足。Greenplum单机性能较差,不适用于查询场景;Clickhouse 虽然在单表查询性能表现不错,但是不支持 MySQL 协议,多表Join无法发挥性能,Greenplum和Clickhouse这两款产品均不能满足对于海量数据在多表关联场景下的查询诉求。

    在AP(分析)业务中,不同于以点查为主的TP(事务)业务 ,事实表和维度表的关联操作不可避免。但在一些灵活度要求较高的场景,比如订单的状态需要频繁改变,或者说业务人员的自助BI分析,宽表往往无法满足需求,还需要使用更为灵活的星型或者雪花模型进行建模。

   ClickHouse虽然提供了Join的的语义,但使用上对大宽表关联的能力支撑较弱,复杂的关联查询经常会引起 OOM,所以如果使用了ClickHouse,需要再ETL的过程中就将事实表与维度表打平成宽表。而Doris 提供了Shuffle Join,Colocate  Join,Broadcast Join、Bucket Shuffle Join 等多种Join模式,对于提升联表查询场景性能有着非常大的优势。

   Doris的优点:

  • 开发简易方便:Doris不仅兼容 MySQL 协议,还能够支持标准的 SQL 语法使开发简单方便。
  • 复杂场景多表关联查询性能:Doris 支持分布式 Join、明细聚合等方式,在进行多表 Join 时能够提供多种优化机制,提升查询效率。同时Doris还支持物化视图与索引功能来完成预计算效果,在命中物化视图时实现快速查询响应。
  • 运维简单、方便扩展:Doris的整体部署只有 FE与 BE 两种角色,极大简化了架构链路,使架构无需再依赖其他组件,实现低成本运维。

Doris的Join类型

 详细链接见第6.5章:Doris查询加速——Join优化原理_doris join 慢-CSDN博客

总览:

  作为分布式的MPP数据库,在Join的过程中是需要进行数据的Shuffle,数据需要拆分调度,才能保证最终的Join结果是正确的。

  目前Doris支持的Join方式有 Broadcast Join、Shuffle Join、Bucket Shuffle Join 和Colocate Join 这4种,这4种方式灵活度和适用性是从高到低的,对数据分布的要求越来越严,但Join计算的性能则通过降低网络开销而越来越好。
   Join 方式的选择是FE生成分布式计划阶段会考虑的事项之一。在FE进行分布式计划时,优先选择的顺序为:Colocate Join -> Bucket Shuffle Join -> Broadcast Join -> Shuffle Join。很明显,Colocate 以及 Bucket Shuffle 是可遇不可求的。当无法使用它们时,Doris会自动尝试进行Broadcast  Join(小表join大表,小表进行广播),如果预估小表过大则会自动切换至 Shuffle Join。

   但是用户也可以通过显式Hint来强制使用期望的 Join 类型,比如:

select k1 from t1 join [BUCKET] t2 on t1.k1 = t2.k2 group by t2.k2;

 Broadcast Join

SELECT * FROM A,B WHERE A.column=B.column

   通过将B表的数据全量广播到A表的机器上,在A表的机器上进行Join操作,相比较于Shuffle join 节省了A表数据Shuffle,但是B表的数据是全量广播,适合B表是个小表的场景。

    如下图,根据数据分布,查询规划出A表有3个执行的HashJoinNode,那么需要将B表全量的发送到3个HashJoinNode,那么它的网络开销是3B,它的内存开销也是3B。

  • 优点:网络开销3B
  • 缺点:内存占用多(每个BE都需要把表B放在内存里面),内存开销3B

     由于内存占用较多,如果表B的数据量够大,那就做不了Broadcast Join(适用于大表join小表)了,所以Doris引入了Shuffle Join​

Shuffle Join

SELECT * FROM A,B WHERE A.column=B.column

   Shuffle Join是根据表A和表B执行join的列进行hash,相同hash值的数据分发到同一个节点上。它的网络开销是A+B,内存开销是B。 

  • 优点:常规join,常用于大数据量的场景
  • 缺点:网络开销多(需要将表A和B进行网络传输),网络开销:A+B

Bucket Shuffle Join

SELECT * FROM A,B WHERE A.distributekey=B.anycolumn

  基于上面Broadcast Join、 Shuffle Join网络传输开销的痛点,Doris引入了更好的操作:Bucket shuffle join。Bucket Shuffle Join是在Broadcast的基础上进一步优化,将B表按照A表的分布方式,Shuffle到A表机器上进行Join操作,B表Shuffle的数据量全局只有一份,比Broadcast少传输了很多倍数量。所以它的网络开销是B,内存开销是B。

    Doris的表数据本身是通过 Hash 计算分桶的,假如两张表需要做 Join,并且 Join 列是左表的分桶列,那么左表的数据不用移动,B表按照A表的分布方式,Shuffle到A表机器上进行Join操作,B表Shuffle的数据量全局只有一份,比Broadcast少传输了很多倍数量。它的网络开销是B,内存开销是B。 Bucket Shuffle Join 通过对左表实现本地性计算优化,来减少左表数据在节点间的传输耗时,从而加速查询。

  • 优点:性能好
  • 缺点:场景有限,需要使用 A.distributekey

      既然表A可以不移动数据,那么表B是不是也能够这样?所以Doris又引入了Colocate join

Colocation Join

SELECT* FROM A,B WHERE A.colocatecolumn=B.collocatecolumn

   它与Bucket Shuffle Join相似,通过建表时指定 A 表和B表是同一个 Colocate Group,确保 A、B 表的数据分布完全一致,那么,计算节点只需做本地 Join,减少跨节点的数据移动和网络传输开销,提高查询性能。Colocate Join 十分适合几张大表按照相同字段分桶的场景,这样可以将数据预先存储到相同的分桶中,实现本地计算。所以它的网络开销是0,数据已经预先分区,直接在本地进行Join计算。

  • 优点:性能最好
  • 缺点:使用场景有限,要求表A和表B的分布是在一个group的,且join的条件跟group是match的。这样的话表A和表B都不需要shuffle了,所以性能也是最好的

参考文章:

Docs

  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值