GPDB-内核特性-动态分区裁剪
上文我们介绍了,GP7中ORCA不再支持动态分区裁剪。那么他的动态分区裁剪效果又是怎么实现的呢?GP7除ORCA优化器外还有PG优化器,他的动态分区裁剪执行计划由PG优化器生成。
1、PartitionSelectorState结构
动态分区裁剪的算子为PartitionSelector,我们看下相关数据结构:
执行器执行过程中,使用PartitionSelectorState来描述相关执行信息。其中Bitmapset part_prune_result为裁剪后分区表,也就是Append算子需要执行的子执行计划。PlanState ps中Plan *plan其实是PartitionSelector,除了Plan结构外,还有paramid,用来表示es_param_exec_vals[]数组的索引,也就是指向该PartitionSelectorState的地址。
Estate *state为整个执行计划树所共享,所以Append节点也可以访问到es_param_exec_vals[]数组,得到对应的PartitionSelectorState。如此,Append节点就可以得到分区裁剪的结果part_prune_result,并据此来选择扫描对应分区。
2、PartitionSelector算子
PartitionSelector算子扫描子节点,然后执行ExecAddMatchingSubPlans进行分区裁剪,裁剪结果存入part_prune_result bitmap中。扫描完后,会将PartitionSelector算子的PartitionSelectorState地址保存到es_param_exec_vals[ps->paramid]数组。
这就需要分析ps->paramid和Append的join_prune_paramids链表是怎么对应的。
3、Append算子
Estate*state::es_param_exec_vals[]是一个数组,而Append节点是如何定位到分区裁剪的PartitionSelectorState位于哪个数组中呢?也就是Append节点执行时,如何选择哪个子分区去执行呢?
1)看下上图第3步,ExecAppend首先通过node->choose_next_subplan选择一个子分区计划去执行
2)然后进入循环,扫描各个裁剪后的分区表,返回扫描到的tuple。
3)第4步,choose_next_subplan_locally函数完成找出匹配的分区子计划,由函数ExecFindMatchingSubPlans函数完成。主要看其入参:plan->join_prune_pramids。
4)第5步,ExecFindMatchingSubPlans函数从plan->join_prune_paramids链表中拿到paramid值,此值作为数组索引,取到estate->es_param_exec_vals[paramid]。该值的value是个Datum类型,即PartitionSelectorState的地址。PartitionSelector算子会将动态裁剪后的分区序号放入part_prune_result中,据此可以在Append节点中获取到PartitionSelector算子的结果。
5)现在需要聚焦plan->join_prune_paramids链表是怎么存储PartitionSelector算子地址的。
6)第1步,create_nestloop_plan函数:push_partition_selector_candidate_for_join函数将candidate->selectors初始化NULL,需要继续分析下面的函数。create_plan_recurse会调用create_append_plan初始化Append计划节点。
7)第2步,create_append_plan函数:root->partition_selector_candidates链表 --> root->partition_selector_candidates->selectors::psinfo放到该selectors链表(psinfo->paramid即分配的paramid)
8)返回第1步,create_append_plan执行完后,从root->partition_selector_candidates中取出psinfo,调用create_partition_selector_path创建PartitionSelector算子的Plan路径,psinfo的paramid会保存到PartitionSelector的paramid中。
9)第1步,执行完outerjoinpath的Plan创建,接着递归调用create_plan_recurse对innerjoinpath创建Plan,此时会调用create_partition_selector_plan创建其Plan:
将PartitionSelectorPath中的paramid赋值给ParitionSelector的paramid。这样ParitionSelector的paramid来自PartitionSelectorPath中的paramid,PartitionSelectorPath中的paramid来自root->partition_selector_candidates链表中的selectors链表(selectors链表的ParitionSelectorInfo中包含分配的paramid),ParitionSelectorInfo的paramid来自create_append_plan函数分配,并将其保存到root->partition_selector_candidates链表中的selectors链表。
4、总结
create_append_plan分配paramid,将其赋值给PartitionSelector的paramid;并将其放到Append的join_prune_paramids链表。PartitionSelector算子将分区表裁剪的结果放到part_prune_result中,并将其PartitionSelectorState的地址存入estate->es_param_exec_vals[paramid]数组。Append算子执行时,根据join_prune_paramids中的paramid获取estate->es_param_exec_vals[paramid],从而得到PartitionSelectorState,继而得到分区裁剪结果part_prune_result。由此,选择appendplans[]数组中的子计划执行。这样就完成了分区裁剪,仅扫描满足条件的子分区。