国产数据库 - 内核特性 - CloudberryDB中的Runtime Filter
今年5月份GreenPlum官方将GitHub仓库代码全部删除,各个分支的issues和bugs讨论等信息全部清除,仅将master分支代码进行归档。对于国内应用GPDB的用户来说,这是一个挑战性事件,对与后期维护、升级等都变得非常困难。有幸HashData开源了基于GP衍生版本CloudberryDB版本,对国内GP用户来说是一个及时的福音。
今天介绍下CloudberryDB中的Runtime Filter。它实现了两种runtime filter方式。一种是:新增了RuntimeFilter算子,在Hash Join算子的探测端添加RuntimeFilter算子,当然这就导致仅在RuntimeFilter算子实现提前过滤,并未将filter下沉到SeqScan算子或者TableAM层,仍旧存在不必要的算子计算。另一种是将runtime filter下推到SeqScan或者TableAM,尽量能够提前终止算子执行。后一种方式目前仅处于开发阶段,并未release,期待该功能尽快完善。
1、RuntimeFilter算子方式过滤
从上面执行几乎也可以看出,仅在Hash Join的探测端挂载了一个RuntimeFilter算子。首先看下该算子是怎么执行的。
1.1 结构体之间关系
主要关系是:HashJoin的运行时结构体HashJoinState的JoinState js即PlanState ps中有左右子节点的执行计划节点。左子树为探测端结构体RuntimeFilterState,执行运行时过滤的动作;右子树为HashState节点,rfstate为RuntimeFilterState地址。由此保证内表构建时,构建的bloom bitmap可以关联到探测端扫描外表时判断外表值是否在bloom bitmap中。
1.2 具体流程
1)MultiExecPrivateHash构建完hash表后,标记build_finish为true,确保RuntimeFilter节点执行时可以进入布隆过滤
2)MultiExecPrivateHash构建hash表时,调用ExecHashGetHashValue将内表值的join字段hash后放到bf中
3)ExecRuntimeFilter执行时,判断外表值是否在bf中,若在则将其输出,若不在则过滤掉,不进入join
4)可以看到,这种运行时过滤方式,仅将过滤下沉了一个执行节点,底层节点的扫描等多层执行计划节点并没有最优地避免执行,效果也不会太好。
2、filter下沉到SeqScan的方式
我们看下另一种实现方式,将布隆过滤下沉到SeqScan底层节点,这种方式比较彻底,可以尽最大可能减少不必要节点执行。
该patch可查看:
https://github.com/cloudberrydb/cloudberrydb/pull/405
Hash执行时构建布隆过滤器的流程如下图所示:
1)通过gp_enable_runtime_filter_pushdown配置项开启该功能
2)BuildRuntimeFilter函数用于构建布隆过滤器,对于每个外表值都将其构建到af->bf中
3)当内表值都构建到hash表,并完成布隆过滤器的build后,通过PushdownRuntimeFilter函数将布隆过滤器通过scankey的形式下推到SeqScan节点的filters链表中。af->target即为SeqScan节点。
SeqScan节点执行时即可通过布隆过滤器进行过滤,流程如下图所示:
1)对于表的每行记录都通过PassByBloomFilter进行判断,看它在没在bloom bitmap中,若在就向上层节点推出记录,否则过滤掉,扫描下个记录。由此可见,在hash join中将join条件过滤提前到了SeqScan位置,大大减少了中间算子的计算。
2)还需要考虑一个问题:如何将HashJoin的Hash子节点的布隆过滤器和下沉对象SeqScan关联起来。
1)HashJoin算子初始化时ExecInitHashJoin通过CreateRuntimeFilter函数构建布隆过滤器和下沉位置的关系
2)通过FindTargetAttr函数遍历HashJoin的左子树,找到SeqScan节点,该节点即为下沉到的对象
3)HashState中的filters链表存储AttrFilter *af。af->bf和af->target构成了布隆过滤器和SeqScan节点的关系。
由此,可见这种方式实现原理上比较完善,可以尽可能的减少中间算子的计算。当然,对于CloudberryDB来说,该功能还未release,期待尽早release。