导读:欢迎来到 StarRocks 源码解析系列文章,我们将为你全方位揭晓 StarRocks 背后的技术原理和实践细节,助你逐步了解这款明星开源数据库产品。 本期 StarRocks 技术内幕将介绍 Join Reorder 算法如何找到最优解的原理。

背景介绍

多表 Join 是现实业务场景中很常见的需求,其执行效率和 Join 的执行顺序息息相关,比如两表 t1 Join t2 就有 t1t2t2t1 两种方式(Join 满足交换律),三表 t1 Join t2 Join t3 由于 Join 满足结合律,可以 t1 和 t2 先做Join,再和 t3 Join,即(t1t2) ⨝ t3, 也可以先做 t2 和 t3 的 Join,再和 t1 做 Join,即 t1 ⨝ (t2t3)。

如上图所示,Table A 和 B 的 Join 会生成较大的 Join 中间结果集,使用 Join Reorder 算法优化后,结果集缩小显著。Join 的执行顺序和执行方式对查询性能的结果影响非常明显,部分场景下甚至能带来数量级的差异,因此优化器选择出好的 Join 顺序尤为重要。

整体流程

刚才提到 Join 是满足交换和结合律的,因此通过 Join 的交换结合,可以拓展出所有的 Join 顺序。在 “StarRocks 优化器代码导读”中我们介绍过,StarRocks 优化器使用 Memo 进行空间搜索,通过使用 transform rule 来完成 GroupExpression 的转换,使用 JoinCommutativity 和 JoinAssociativity 两个 rule 完成 Join 的交换和结合。先来看张图,可以帮助你理解:

上图罗列了三表 A、B、C 所有可能的 Join 顺序,这些在 Memo 中表示成不同的 GroupExpression,并记录在同一个 Group 中。注意,这些 Join 顺序不同的 GroupExpression 是逻辑等价的。

理论上我们可以使用 Join 的交换结合枚举出所有的 Plan,通过计算每个 Plan 的 Cost,从而选出代价最小的 Plan。但实际上随着 Join 节点的增多,优化器的搜索空间会成指数级放大。如下图所示:

​随着 Join 节点的增多,优化器将无法枚举出所有 Plan。另一方面,优化器需要在有限时间内给出最优解,因此我们需要使用高效的 Join Reorder 算法来决定 Join 的顺序。 在无法枚举所有的 Join 顺序时,StarRocks 使用了贪心和动态规划两种算法来决定多表 Join 的顺序,具体策略是:

  1. Plan 中 Join 节点小于等于 4(可通过 session 变量 cbo_max_reorder_node_use_exhaustive 修改)个时,使用枚举的方法决定Join顺序。

  2. Plan 中 Join 节点大于 4,小于等于 10(可通过 session 变量 cbo_max_reorder_node_use_dp 修改)个 Join 节点时,使用动态规划和贪心算法决定 Join 顺序。

  3. Plan中 Join 节点大于 10个的时候,使用贪心算法决定 Join 的顺序。

  4. 如果 Plan 中的 Scan 节点包含未知的列统计信息,将只生成默认的左深树。

枚举的方法可以通过 Cascades 框架估算出分布式计划的代价,DP 和贪心算法生成的 Plan 则需要通过 Memo 的 Property Enforce 实现,因此所有通过贪心和 DP 生成的 Plan Tree 都需要 Copy In Memo,参与后续搜索空间的拓展并计算分布式 Plan 的 Cost。

DP 和贪心算法选择出的 Join Order 是单机最优 Plan,为了尽量找到最优的分布式 Plan,StarRocks 在Join 个数不超过cbo_max_reorder_node_use_dp 时,会同时保留 DP 和贪心的 Plan,且贪心算法也会保留 Cost 最小的 10 个 Plan,为后续找到“最优”分布式 Plan 提供更多的可能性。 接下来,我们将详细介绍相关代码。

代码导读

Join 交换结合

Join 的交换和结合律的使用,是基于 Cascades 优化框架实现的,因此在 StarRocks 中只需要实现对应的 Transform Rule,就可以完成 Join 的交换和结合。 通过 Join 的交换和结合,可以找到所有逻辑等价的 GroupExpression。Join 的交换通过 JoinCommutativityRule 完成,逻辑比较简单,就是将孩子的左右孩子节点互换。 需要注意的是,并不是只有 Inner Join 和 Cross Join 才可以进行互换,Outer Join 和 SemiJoin 同样可以。JoinCommutativityRule 中通过一个 Map 记录了 Join Type 发生交换时的改变,代码如下:

private static final Map<JoinOperator, JoinOperator> Join_COMMUTATIVITY_MAP =ImmutableMap.<JoinOperator, JoinOperator>builder().put(JoinOperator.LEFT_ANTI_Join, JoinOperator.RIGHT_ANTI_Join).put(JoinOperator.RIGHT_ANTI_Join, JoinOperator.LEFT_ANTI_Join).put(JoinOperator.LEFT_SEMI_Join, JoinOperator.RIGHT_SEMI_Join).put(JoinOperator.RIGHT_SEMI_Join, JoinOperator.LEFT_SEMI_Join).put(JoinOperator.LEFT_OUTER_Join, JoinOperator.RIGHT_OUTER_Join).put(JoinOperator.RIGHT_OUTER_Join, JoinOperator.LEFT_OUTER_Join).put(JoinOperator.INNER_Join, JoinOperator.INNER_Join).put(JoinOperator.CROSS_Join, JoinOperator.CROSS_Join).put(JoinOperator.FULL_OUTER_Join, JoinOperator.FULL_OUTER_Join).build();

例如 left outer Join 的孩子节点交换时,需要从 left outer 转换成 right outer。代码如下:

       left outer Join             right outer Join/           \      =>        /            \A             B              B              A

Join 的结合通过 JoinAssociativityRule 完成,主要逻辑可以用下面的图表示,Join 顺序的改变在Plan 中的改变就是树的形状变化。当然,在生成新的 OptExression 的过程中,也需要考虑 predicate 和 project 的重新分配。StarRocks 为 Join On 条件中包含表达式的结合转换做了支持,例如 SQL:

Select C.v4 from A Join B on A.v1 = B.v2 Join C on B.v2+1=C.v4 and B.v3 = C.V5

Join 上的 predicate 使用的列需要在孩子节点中包含,对于有 project 的进行表达式计算的,也需要考虑将其放在合适的 Join 上。 例如下图中,须将 B.v3 = C.v5 放在新生成的 Join 节点上,如果 Table B 和 Table C 之间没有等值的谓词连接条件,StarRocks 会禁止转换,避免生成 CrossJoin 节点Project 节点也需要在新生成的 Join 节点上重新计算,保证向上输出上层 Join 节点所需的 Column

MultiJoinNode

为了加速多表(StarRocks 中为多于 4 表)Join reorder 的处理,StarRocks 中使用了 MultiJoinNode 来表示多张表的 Join。可以化简为以下代码:

public class MultiJoinNode {// Atom: A child of the Multi Join. This could be a table or some// other operator like a group by or a full outer Join.private final LinkedHashSet<OptExpression> atoms;private final List<ScalarOperator> predicates;private Map<ColumnRefOperator, ScalarOperator> expressionMap;public MultiJoinNode(LinkedHashSet<OptExpression> atoms, List<ScalarOperator> predicates,Map<ColumnRefOperator, ScalarOperator> expressionMap) {this.atoms = atoms;this.predicates = predicates;this.expressionMap = expressionMap;}}

将多个 InnerJoin/CrossJoin 节点转换成 MultiJoinNode,其中需要 reorder 的孩子节点都表示成atoms。如下图所示,table A、B、C、D 就是各个 atom,所有的谓词记录在 predicates 中,后续算法将基于 MultiJoinNode 对 atom 进行重新组合,以产生合适的顺序。

左深树

如下图所示,左侧为左深树,右侧为稠密树。在单机/单任务数据库上,只需考虑左深树就可完成 Join 重排。当无法获取表的列统计信息时,无法准确估算 Join 的中间结果集,因此 StarRocks 选择了只生成左深树。

左深树的生成是由 JoinReorderLeftDeep 类完成的,逻辑比较简单。StarRocks 的 HashJoin 都是右表 build,左表 probe,期望生成的 Plan 中右表应该是小表,此方法中将需要 reorder 的 atom 按照 row count 从大到小进行排序,树最深的节点是代价最高的节点,这种 reorder 方法在没有列统计时也可以获得较为不错的表现。

动态规划

StarRocks 使用的是 DPsub 算法,通过将 atoms 划分成不同的 Partitions,递归计算子 Patition 的 bestPlan,并记录在 bestPlanMemo 中,从而实现规避重复计算。

例如,下图中的 [A,B] 就可以直接从 bestPlanMemo 中得到,而 [C,D] 则需要进一步递归分别得到 [C] 和 [D] 的 Best Plan,再计算 Join order [C,D] 的 Cost,并将 Cost 最小的 Join order 插入到 BestPlanMemo 中。最终包含所有 atom 的 Partition 即为 DP 算法选出的最佳 Plan。

这种 Bottom-Up 的 DP 算法可以有效处理稠密树空间枚举的问题,并能够利用动态规划来解决中间结果重复计算的问题,但由于需要枚举出所有子 Partition 的 Best Plan,atoms 过大时会导致优化时间太长,因此我们默认 10 个 Join 以内采用此方法。

贪心算法

如图,为贪心算法的实现:

在贪心算法的实现中,StarRocks 将 Join 分为了多个 Level。第一层就是每个 atom,从第一层中选择 Row Count 最小 atom 的和其他的 atom Join 生成 Join level 2。

其他 level 类比,Level K 从 Level K-1 中选择 Row Count 最小的,Join 其他的 atom,计算出 Level K 输出的 Row Count。当 K 等于 atom 个数时,算法结束,选出的即为最终“最优”的 Join Order。

这种方法的实现比较简单,贪心算法的原理也比较好理解,但不足之处在于只能用来构建左深树,且第一个 atom 的选择会对 Join Order 产生比较大的影响,容易陷入局部最优的问题。为了缓解这一问题,StarRocks 会启发式地生成 K 个 join 顺序,每个 Join 顺序选择的第一个 atom 都不一样,并将这些生成的 Plan 都 Copy In Memo,参与后续的 cost 计算,并从中选择 cost 最低的 plan。

总结

本文主要介绍了 StarRocks 中使用的 Join Reorder 算法和其基本原理。依据 Join 节点的个数不同,我们选用不同的 Join Reorder 算法,较少时用枚举法,10 个以内 Join 节点使用 DP 和贪心算法,超过 10 个时只使用贪心算法。通过对多种算法的使用,StarRocks 可以在 Join 较少时迅速找到最优解,在 Join 较多时也能在相对较短的时间内产生效果不错的 Plan。 此外,为避免 Join Reorder 后的 Plan 只是单机最优,StarRocks 中还保留了多个算法产生 Join Order,以尽可能在 Memo 中找到分布式的最优解。

本期 StarRocks 源码解析到这就结束了,好学的你肯定学会了一些新东西,又产生了一些新困惑,不妨留言评论或者加入我们的社区一起交流(StarRocks 小助手微信号)。下一篇 StarRocks 源码解析,我们将为你带来 StarRocks 统计信息和 Cost 估算。

StarRocks Join Reorder 源码解析相关推荐

  1. [源码解析] PyTorch分布式优化器(1)----基石篇

    [源码解析] PyTorch分布式优化器(1)----基石篇 文章目录 [源码解析] PyTorch分布式优化器(1)----基石篇 0x00 摘要 0x01 从问题出发 1.1 示例 1.2 问题点 ...

  2. StarRocks Parser 源码解析

    导读:欢迎来到 StarRocks 源码解析系列文章,我们将为你全方位揭晓 StarRocks 背后的技术原理和实践细节,助你逐步了解这款明星开源数据库产品.本期将主要介绍 StarRocks Par ...

  3. StarRocks Analyzer 源码解析

    导读:欢迎来到 StarRocks 源码解析系列文章,我们将为你全方位揭晓 StarRocks 背后的技术原理和实践细节,助你逐步了解这款明星开源数据库产品.本期将主要介绍 StarRocks Par ...

  4. Apache Sedona(GeoSpark) spatial join 源码解析

    文章目录 Apache Sedona(GeoSpark) Spatial Join Range join Distance join 源码解析 SedonSQLRegistrator.register ...

  5. 谷歌BERT预训练源码解析(三):训练过程

    目录 前言 源码解析 主函数 自定义模型 遮蔽词预测 下一句预测 规范化数据集 前言 本部分介绍BERT训练过程,BERT模型训练过程是在自己的TPU上进行的,这部分我没做过研究所以不做深入探讨.BE ...

  6. 谷歌BERT预训练源码解析(一):训练数据生成

    目录 预训练源码结构简介 输入输出 源码解析 参数 主函数 创建训练实例 下一句预测&实例生成 随机遮蔽 输出 结果一览 预训练源码结构简介 关于BERT,简单来说,它是一个基于Transfo ...

  7. Simple Dynamic Strings(SDS)源码解析和使用说明二

    在<Simple Dynamic Strings(SDS)源码解析和使用说明一>文中,我们分析了SDS库中数据的基本结构和创建.释放等方法.本文将介绍其一些其他方法及实现.(转载请指明出于 ...

  8. 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...

  9. python flask源码解析_用尽洪荒之力学习Flask源码

    [TOC] 一直想做源码阅读这件事,总感觉难度太高时间太少,可望不可见.最近正好时间充裕,决定试试做一下,并记录一下学习心得. 首先说明一下,本文研究的Flask版本是0.12. 首先做个小示例,在p ...

最新文章

  1. LeetCode简单题之同构字符串
  2. Django 图片上传upload_to路径指定失效的问题记录
  3. 使用MyEclipse简单调用WebServices
  4. 如何卸载MySQL8.0.11_win10安装mysql8.0.11卸载5.7
  5. 【紫书】(UVa12096) The SetStack Computer
  6. 「leetcode」257. 二叉树的所有路径(详解)
  7. JAVA 内部类 泛型 实现堆栈
  8. 复制构造函数被调用的3种情况
  9. jsp实现简单购物车过程
  10. Hydra(弱密码爆破)使用教程
  11. 写入clickhouse效率低总结
  12. WebStorm中TODO的作用
  13. Flex ANE制作打包流程
  14. 计算机快捷键如何移动到桌面,如何设置显示桌面快捷键 设置显示桌面快捷键方法【图文】...
  15. 信息化项目WBS实战总结
  16. 51Nod 1278 相离的圆 c/c++题解
  17. 网站安全公司waf防火墙的作用分析
  18. OracleConnection.ConnectionString
  19. java使用knn实现mnist_java使用knn实现mnist - 百度学术
  20. python - alipay sdk 使用 及 注意点

热门文章

  1. 管家显示服务器维修,服务器常见的11种基本故障及排查方法汇总介绍
  2. ESP32 学习笔记(六)I2C - Inter-Integrated Circuit
  3. jQuery -- 光阴似箭(二):jQuery效果的使用
  4. ESD导致空调控制面板控制失效问题分析与解决
  5. MCS-51单片机外部引脚及总线接口/答疑
  6. 怀念 The king of pop -- 迈克尔·杰克逊(MichaelJackson)
  7. 批量给pdf增加水印
  8. 联易融港交所上市:金融、科技一把抓,研发投入不及明源云等
  9. ZZW原创_rsync同步时由于注释问题引起的@ERROR: chdir failed
  10. 回归前端学习第9天——JS的Promise承诺