GaussDB(DWS)分布式Join原理浅析

举报
Arrow0lf 发表于 2021/06/01 16:43:57 2021/06/01
【摘要】 本文主要简单介绍分布式场景下两表join的原理和几种典型场景,并提供几点减少stream算子的优化建议

前言

单机场景下两表关联(join)比较简单,但是分布式场景下,由于数据分布在不同的Datanode(DN),DWS是如何对两表进行Join的呢?为什么有时候两表关联时会出现Stream算子,有时候没有呢?接下来,本文简单介绍下GaussDB(DWS)在分布式下的两表Join的实现方法。

几种典型场景

GaussDB(DWS)当前支持表,Hash分布表和复制表。其中Hash表的数据根据分布键的Hash值分布在不同DN,复制表在每个DN节点之间都有全量数据。因此,DWS中两表join可以分为如下几种场景。

说明:前置场景如下,其中t1、t2表为Hash表,tt1、tt2表为复制表,为了方便执行计划展示,已设置enable_fast_query_shipping为off,建表语句如下:

create table t1(a int, b int, c int) distribute by hash(a);
create table t2(a int, b int, c int) distribute by hash(a);
create table tt1(a int, b int, c int) distribute by replication;
create table tt2(a int, b int, c int) distribute by replication;

场景一:Hash表和Hash表Join

分布式场景下Hash表和Hash表进行关联的核心思想总结成一句话就是:两表满足join条件的数据分布在同一个DN上,不在同一个DN上的数据一定不满足join条件

1. join列都包含分布列

这种场景本身就满足核心思想:两表满足join条件的数据分布在同一个DN上,不在同一个DN上的数据一定不满足join条件,因此可以直接在各DN上进行Join,各DN执行完成后结果返回给CN,执行计划如下:

postgres=# explain select * from t1 left join t2 on t1.a = t2.a;
                                 QUERY PLAN                                  
-----------------------------------------------------------------------------
  id |          operation           | E-rows | E-memory | E-width | E-costs 
 ----+------------------------------+--------+----------+---------+---------
   1 | ->  Streaming (type: GATHER) |     20 |          |      24 | 32.56   
   2 |    ->  Hash Left Join (3, 4) |     20 | 1MB      |      24 | 26.56   
   3 |       ->  Seq Scan on t1     |     20 | 1MB      |      12 | 13.13   
   4 |       ->  Hash               |     21 | 16MB     |      12 | 13.13   
   5 |          ->  Seq Scan on t2  |     20 | 1MB      |      12 | 13.13   
 
 Predicate Information (identified by plan id)
 ---------------------------------------------
   2 --Hash Left Join (3, 4)
         Hash Cond: (t1.a = t2.a)

2. join条件只包含一张表的分布列

该场景下,为了满足核心思想,需要将不包含分布列的一侧做重分布,从而满足要求。执行计划如下:

postgres=# explain select * from t1 left join t2 on t1.b = t2.a;
                                       QUERY PLAN                                       
----------------------------------------------------------------------------------------
  id |                operation                | E-rows | E-memory | E-width | E-costs 
 ----+-----------------------------------------+--------+----------+---------+---------
   1 | ->  Streaming (type: GATHER)            |     20 |          |      24 | 33.75   
   2 |    ->  Hash Left Join (3, 5)            |     20 | 1MB      |      24 | 27.75   
   3 |       ->  Streaming(type: REDISTRIBUTE) |     20 | 2MB      |      12 | 14.31   
   4 |          ->  Seq Scan on t1             |     20 | 1MB      |      12 | 13.13   
   5 |       ->  Hash                          |     21 | 16MB     |      12 | 13.13   
   6 |          ->  Seq Scan on t2             |     20 | 1MB      |      12 | 13.13   
 
 Predicate Information (identified by plan id)
 ---------------------------------------------
   2 --Hash Left Join (3, 5)
         Hash Cond: (t1.b = t2.a)

3. join条件不包含分布列

同理,该场景下,由于join条件没有包含分布键,因此为了满足核心思想,需要对两表都做redistribute,执行计划如下:

postgres=# explain select * from t1 left join t2 on t1.b = t2.b;
                                        QUERY PLAN                                         
-------------------------------------------------------------------------------------------
  id |                 operation                  | E-rows | E-memory | E-width | E-costs 
 ----+--------------------------------------------+--------+----------+---------+---------
   1 | ->  Streaming (type: GATHER)               |     20 |          |      24 | 34.93   
   2 |    ->  Hash Left Join (3, 5)               |     20 | 1MB      |      24 | 28.93   
   3 |       ->  Streaming(type: REDISTRIBUTE)    |     20 | 2MB      |      12 | 14.31   
   4 |          ->  Seq Scan on t1                |     20 | 1MB      |      12 | 13.13   
   5 |       ->  Hash                             |     21 | 16MB     |      12 | 14.31   
   6 |          ->  Streaming(type: REDISTRIBUTE) |     20 | 2MB      |      12 | 14.31   
   7 |             ->  Seq Scan on t2             |     20 | 1MB      |      12 | 13.13   
 
 Predicate Information (identified by plan id)
 ---------------------------------------------
   2 --Hash Left Join (3, 5)
         Hash Cond: (t1.b = t2.b)

场景二:复制表和复制表Join

由于复制表在每个DN上有一份全量数据,该场景下相当于单机场景,任选一个DN进行两遍关联即可,不做赘述。

场景三:Hash表和复制表Join

1. Hash表left join 复制表

hash表left join复制表的时候,由于复制表在每个DN上都有一份全量数据,因此不管join条件是否包含hash表的分布键,都不会产生redistribute算子。

postgres=# explain select * from t1 left join tt1 on t1.b = tt1.b;
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
  id |           operation           | E-rows | E-memory | E-width | E-costs 
 ----+-------------------------------+--------+----------+---------+---------
   1 | ->  Streaming (type: GATHER)  |     20 |          |      24 | 39.66   
   2 |    ->  Hash Right Join (3, 4) |     20 | 1MB      |      24 | 33.66   
   3 |       ->  Seq Scan on tt1     |     40 | 1MB      |      12 | 20.20   
   4 |       ->  Hash                |     21 | 16MB     |      12 | 13.13   
   5 |          ->  Seq Scan on t1   |     20 | 1MB      |      12 | 13.13   
 
 Predicate Information (identified by plan id)
 ---------------------------------------------
   2 --Hash Right Join (3, 4)
         Hash Cond: (tt1.b = t1.b)

2. 复制表left join hash表

这种场景下,如果join列不包含hash表的分布列,是会产生redistribute算子的,可以想想为什么(提示:和left join的补NULL有关)。

Redistribute还是Broadcast?

前面介绍的几种场景中,出现的stream算子都是redistribute,什么时候会出现broadcast算子呢?试想一下场景一中的第三种情况,join两侧都需要redistribute,实际场景中,left join的左侧一般都很大,数据量很多,如果集群规模也很大,这时对左表做redistribute的代价是比较高的,这种情况下,如果右侧的表恰好又比较小,那么可以对右侧表做Broadcast,减少大表重分布的开销,达到如下效果:

右侧的小表做Broadcast后,相当于复制表,可以和左侧的表直接Join,执行计划如下:

postgres=# explain select * from t1 left join t2 on t1.b = t2.b;
                                       QUERY PLAN                                       
----------------------------------------------------------------------------------------
  id |                operation                | E-rows | E-memory | E-width | E-costs 
 ----+-----------------------------------------+--------+----------+---------+---------
   1 | ->  Streaming (type: GATHER)            |    100 |          |      24 | 24.07   
   2 |    ->  Hash Left Join (3, 4)            |    100 | 1MB      |      24 | 18.07   
   3 |       ->  Seq Scan on t1                |    100 | 1MB      |      12 | 2.50    
   4 |       ->  Hash                          |     40 | 16MB     |      12 | 15.18   
   5 |          ->  Streaming(type: BROADCAST) |     40 | 2MB      |      12 | 15.18   
   6 |             ->  Seq Scan on t2          |     20 | 1MB      |      12 | 13.13   
 
 Predicate Information (identified by plan id)
 ---------------------------------------------
   2 --Hash Left Join (3, 4)
         Hash Cond: (t1.b = t2.b)

两表Join场景下减少Stream算子的优化建议

大集群高并发场景下,Stream算子过多可能会导致通信的性能瓶颈,引起性能劣化,因此需要尽可能减少Stream算子,根据上面介绍的几种场景,常用的方法如下:

1. 建表时合理选取分布列

在满足数据均匀分布的基础上,选取经常被用作join条件的列作为分布键,尽量不要使用sequence id等无意义的分布键。是否满足均匀分布可以使用函数table_skewness查看,示例:

postgres=# select * from table_skewness('t1','b');
 seqnum | num |  ratio   
--------+-----+----------
 0      | 100 | 100.000%
(1 row)

结果表明该列倾斜率为100%,不适合做分布列。

2. 合理使用复制表

如果join右侧的维表比较小,且不会频发更新,可以考虑将内表建成复制表。

3. 增加包含分布键的join条件

某些场景下可以适当增加包含分布列的join条件,达到减少stream算子的目的。

想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料~


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。