当前位置:   article > 正文

第6.5章:Doris查询加速——Join优化原理_doris join 慢

doris join 慢

目录

背景

一、Doris数据划分

1.1 分区

1.2 分桶

二、Join 方式

2.1 总览

2.2 Broadcast Join

2.3 Shuffle Join

2.4 Bucket Shuffle Join

2.5 Colocation Join

2.6 四种 Shuffle 方式对比

三、Runtime Filter Join 优化

3.1 Runtime Filter概述

3.2 Runtime Filter Join 原理

3.3  Runtime Filter 类型

3.3.1 IN

3.3.2 Bloom Filter

3.3.3 MinMax

四、Join Reorder优化

4.1 Join Reorder概述

4.2 Join Reorder 算法

五、Join 调优

5.1 Join 调优建议

5.2 Join 调优方法

5.3 调优案例实战

背景

  掌握Doris优化手段及其实现原理,为代码阅读提供理论基础。

一、Doris数据划分

    在介绍Doris中多种Join方式及优化原理之前,先回顾下Doris的数据划分及tablet多副本机制。        Doris 支持两层的数据划分,第一层是Range  Partition,第二层是Hash  Bucket(Tablet)。 Doris的数据表按照分区分桶规则,被水平切分成若干个数据分片(Tablet,也称作数据分桶 Bucket)存储在不同的be节点上,每个tablet都有多个副本(默认是3副本)。各个 Tablet 之间的数据没有交集,并且在物理上是独立存储的。Tablet 是数据移动、复制等操作的最小物理存储单元。

  下图说明 Table、Partition、Bucket(Tablet) 的关系:

  • 假设Table按照 Range的方式按照date字段进行分区,得到了 N 个 Partition
  • 每个 Partition 通过相同的 Hash 方式将其中的数据划分为 M 个 Bucket(Tablet)
  • 从逻辑上来说,Bucket 1 可以包含 N 个 Partition 中划分得到的数据,比如下图中的 Tablet 11、Tablet 21、Tablet N1

1.1 分区

    逻辑概念,分区用于将数据划分成不同的区间,主要作用是将一张表按照分区键拆分成不同的管理单元。查询时,通过分区裁剪,可以减少扫描的数据量,显著优化查询性能。

1.2 分桶

   物理概念,StarRocks一般采用Hash算法作为分桶算法。在同一分区内,分桶键哈希值相同的数据会划分到同一个tablet(数据分片),tablet以多副本冗余的形式存储,是数据均衡和恢复的最⼩单位,数据导入和查询最终都下沉到所涉及的 tablet副本上。

二、Join 方式

2.1 总览

  作为分布式的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;

2.2 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​

2.3 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

2.4 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

  ps: Bucket Shuffle Join的详细内容见文章:

第6.3章:StarRocks查询加速——Bucket Shuffle Join-CSDN博客

2.5 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了,所以性能也是最好的

   ps: Colocation Join的详细内容见文章:

第6.3章:StarRocks查询加速——Colocation Join-CSDN博客

2.6 四种 Shuffle 方式对比

  ps:下表中的A是上文提到的左表,B是上文提到的右表

Shuffle方式    网络开销物理算子适用场景
BroadCast3BHash Join / Nest Loop Join通用
ShuffleA+BHash Join通用
Bucket ShuffleBHash JoinJoin条件中存在左表的分布式列,且左表执行时为单分区
Colocate0Hash JoinJoin条件中存在左表的分布式列,且左右表同属于一个Colocate Group

三、Runtime Filter Join 优化

3.1 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 都可以起到大幅减少数据读取的效果,从而加速整个查询的速度。

3.2 Runtime Filter Join 原理

    Doris 在进行 Hash Join 计算时会在右表构建一个 Hash Table,左表流式地通过右表的 Hash Table 从而得出 Join 结果。而 Runtime Filter 就是充分利用了右表的 Hash Table 构建阶段去做一些额外的事情。

    在右表生成 Hash Table的时候,同时生成一个基于Hash Table数据的一个过滤条件,然后下推到左表的数据扫描节点。通过这样的方式,Doris可以在运行时进行数据过滤。

    假如左表是一张大表,右表是一张小表,那么利用下推到左表的过滤条件就可以把绝大多数 Join 层要过滤的数据在数据读取时就提前过滤,从而大幅度地提升Join查询的性能。

3.3  Runtime Filter 类型

3.3.1 IN

 将一个 hashset 下推到数据扫描节点。

  • 优点:过滤效果明显,且快速。
  • 缺点:只适用于 BroadCast;如果右表超过一定数据量时就失效了,当前 Doris目前配置的是1024,即右表如果大于1024,IN 的 Runtime Filter 就直接失效了。

3.3.2 Bloom Filter

    利用哈希表的数据构造一个 BloomFilter,然后把这个 BloomFilter 下推到查询数据的扫描节点。

  • 优点:通用,适用于各种类型、效果也比较好。
  • 缺点:配置比较复杂并且计算较高。

3.3.3 MinMax

  通过右表数据确定 Range 范围之后,下推给数据扫描节点。

  • 优点:开销比较小
  • 缺点:对数值列还有比较好的效果,但对于非数值列,基本上就没什么效果。

四、Join Reorder优化

4.1 Join Reorder概述

   数据库一旦涉及到多表Join,Join的执行顺序对整个Join查询的性能是影响很大的。Join Reorder 用于推断多表 Join 的执行顺序,数据库需要尽可能地先执行一个高选择度的 Join,这样就能减少后续Join的输入数据,从而提升性能。

4.2 Join Reorder 算法

  Doris 目前的 Join Reorder 算法是基于RBO的,逻辑描述如下:

  • 让大表跟小表尽量做Join,它生成的中间结果时尽可能小的
  • 把有条件的Join表往前放,也就是说尽量让有条件的 Join 表进行过滤
  • Hash Join的优先级高于Nest Loop Join,因为 Hash join 本身是比 Nest Loop Join 快很多的

     总结:前两条都是在朝着让【右表更小的方向去优化,最后一条则是从算法性能上来考虑。

举例说明:

    如下图, Join Reorder推导Join的顺序。把 a 表先与 c 表 Join,生成的中间结果只有 100,然后最终再与 b表Join 计算,虽然最终的Join 结果是一样的,但是它生成的中间结果有 20 倍的差距,这就会产生一个很大的性能差距了。

五、Join 调优

5.1 Join 调优建议

  • Join 列最好是相同的简单类型;同类型避免 Cast 操作,简单类型则有不错的 Join 计算性能

  • Join 列最好是Key列,原因是Key列能够充分利用Doris延迟物化的特性,减少 IO 提升性能

  • 大表之间的Join最好能够利用上Colocate,相当于已经做好了预 Shuffle,实际查询的时候可以直接进行本地Join 计算不再有Shuffle操作,彻底避免了大表的Shuffle网络开销和数据交换

  • 合理的使用 Runtime Filter,它在 Join 过滤率高的场景下效果是非常显著,根据 3 种 Runtime Filter 特点选择最适合的

  • 涉及多表 Join,需要判断 Join 的合理性。尽量保证“左大右小”的原则,HashJoin 优于 NLJ(Nest Loop Join)。必要的时可以通过 SQL Rewrite,利用 Hint 去调整Join的顺序。

5.2 Join 调优方法

  • 利用 Doris 本身提供的 Profile,去定位查询的瓶颈。Profile 会记录 Doris 整个查询当中各种信息,这是进行性能调优的一手资料。
  • 了解Doris中4种Join机制,理解其执行原理后,才能进一步分析它为什么比较慢。
  • 利用Session 变量去改变 Join的一些行为,从而实现Join的调优。例如:当Join的顺序出现了问题,这时候尝试改变 Session 变量,开启 Join Reorder,推断出多表 Join 的执行顺序。
    set enable_cost_based_join_reorder = true
    
  • 查看Query Plan去分析这个调优是否生效。

5.3 调优案例实战

  待补充~

参考文章:

Apache Doris Join 优化原理介绍 - 掘金

Apache Doris Join 实现与调优实践|未来源码 - 掘金

StarRocks 技术内幕 | Join 查询优化

Pipeline 执行引擎 - Apache Doris

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号