天天看点

greenplum分布键的hash值计算分析

greenplum 是一个 MPP 架构的数据库,由一个 master 和多个 segment 组成(还可选配置一个 standby master),其数据会根据设置的分布策略分布到在不同的 segment 上。

在 6 版本中,gp 提供了 3 个策略:随机分布、复制分布、hash 分布。

在创建表的时候,使用 "DISTRIBUTED RANDOMLY" 子句。

该策略会使数据随机分布到各个 segment,即使是完全一样的两行数据,也可能会被分散至不同的 segment。虽然随机分布可以使数据平均的分散至所有的 segment(不会出现数据倾斜),但进行表关联分析时,仍然会按照关联键进行重分布数据,所以该策略在生产环境中很少使用。

在创建表的时候,使用 "DISTRIBUTED REPLICATED" 子句。

该策略会把数据发送至所有的 segment,即所有的 segment 都拥有该表的所有数据,所以在表关联分析时,可以减少数据重分布,但该数据会保存到所有的 segment,所以会产生大量的重复数据。所以,该策略适合一些小表使用。

在重建表的时候,使用 "DISTRIBUTED BY (column,[...])" 子句。

该策略需要用户指定哪些列作为分布键,且分布键必须是主键的子集。gp 会根据分布键的值,进行计算得出 hash key 值,再根据该 key 值计算得出该数据被分配到哪个 segment上。用户可以结合自己的数据特点,以及以后数据分析的规律,为不同的表指定不同的分布键,以提供良好的数据存储以及数据分析性能。

这里直接贴出调用堆栈,重点分析 directDispatchCalculateHash 函数:

这里只贴出 directDispatchCalculateHash 函数的重点代码及注释:

分析:

1、<code>InitFunctionCallInfoData</code> 该宏展开为:

这里主要是用来初始化 Fcinfo 结构体, <code>fcinfo</code> 类型为 <code>FunctionCallInfoData</code>,其定义为: <code>typedef Datum (*PGFunction) (FunctionCallInfo fcinfo);</code>。

<code>FunctionCallInfoData</code>是一个通用的用于传递回调函数的入参结构体,

其中:

a、flinfo 字段是一个结构体,类型为 <code>FmgrInfo</code> ,该结构体里面最重要的是 <code>fn_addr</code> 字段,它存储了后面真正调用的 hash 回调函数的地址。

b、nargs 字段表示回调函数的入参个数,这里固定为1,说明所有的 hash 函数的入参个数都只有1个。

2、 <code>FunctionCallInfoData</code>中的 <code>arg</code> 字段表示回调函数入参列表,这里只使用了 <code>datum</code> 赋值,从外层函数可以看出来,该值即为当前列的值。

所以从这里可以确定,分布键使用的 hash 回调函数的入参通过封装的 <code>FunctionCallInfoData</code>结构体进行传输,且最终里面使用的 hash 函数的入参只有 1 个,就是分布键的值。

3、 <code>FunctionCallInvoke</code> 展开后为 <code>((* (fcinfo)-&gt;flinfo-&gt;fn_addr) (fcinfo))</code> ,即这里真正调用了 hash 回调函数,并使用前面赋值好的 <code>fcinfo</code> 作为参数。

4、最终把 hash 回调函数的返回值强转为 <code>uint32</code> 类型,再与之前计算出来的 hash key 做异或操作后,作为最后的 hash key 保存到当前 cdbHash 环境中的 <code>hash</code> 里,即最后的赋值: <code>h-&gt;hash = hashkey</code>。

外层,先对当前的会话创建一个 hash 环境,然后遍历每个分布键做一次 hash 计算,根据最终的 hash key 值,做一次 reduce,计算出 segment id。

内层,先初始化通用的回调函数入参,再调用回调函数,并与之前的 hash key 值做一次异或操作,得出当前的 hash key。

smallint 类型,对应的 hash 函数是 hashint2,

int 类型,对应的 hash 函数是 hashint4,

bigint 类型,对应的 hash 函数是 hashint8,

具体实现如下:

把宏展开后,可以观察到,smallint 、int 和 bigint 实际上底层调用的 hash 函数都是 hash_uint32,唯一有区别的是 hash_uint32 的入参。

当类型是 smallint 或 int 时,入参就是其本身,而当类型是 bigint 时,该类型长度为8字节,所以需要对其处理一下:当被 hash 的值大于等于0时,则使用高4字节与第4字节异或的值进行 hash;当被 hash 的值小于0时,则使用高4字节的相反数,与低4字节异或的值进行 hash。

char 类型,对应的 hash 函数是 hashbpchar,

text / varchar 类型,对应的 hash 函数是:hashtext,

把上面的宏展开后,对比这三种类型的 hash 函数,其实不难发现,它们的 hash 函数底层都一样,都是通过 hash_any 函数进行计算,入参都是本身字符串值,以及字符串长度。

类型

别名

函数

smallint

int2

hashint2

integer

"int

int4"

bigint

int8

hashint8

bit

bithash

bit varying

varbit

boolean

bool

hashchar

bytea

hashvarlena

character [(n)]

char [(n)]

hashbpchar

character varying [ (n) ]

varchar [(n)]

hashtext

text

cidr

hashinet

date

hashint4

inet

interval

interval_hash

jsonb

jsonb_hash

macaddr

hashmacaddr

numeric

decimal

hash_numeric

real

float4

hashfloat4

time [ without time zone ]

time_hash

time with time zone

timetz_hash

timestamp [ without time zone ]

timestamp_hash

timestamp with time zone

uuid

uuid_hash