GaussDB(DWS)分布式Join原理浅析
前言
单机场景下两表关联(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级数仓黑科技,后台还可获取众多学习资料~

- 点赞
- 收藏
- 关注作者
 
             
           
评论(0)