StarRocks 统计信息和 Cost 估算

news2025/1/10 23:06:37

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

1.背景

在学习本文之前,首先需要对 Optimizer 有一定的背景知识,这里可以参考StarRocks优化器代码导读 。其次本文是基于 StarRocks branch-2.2 的,后面版本的新增功能不在本文范围之内。

统计信息和 Cost 估算是 Optimizer 中的重要部分,准确的统计信息和 Cost Model 可以帮助优化器判断不同执行计划的代价,从而在有限的时间内选择出最优的执行计划。

本文将介绍 StarRocks 统计信息的收集、计算,以及 Optimizer 如何利用统计信息计算 cost,从而找到 cost 最小的 plan。

2.整体流程

我们首先来看统计信息收集和读取的整体流程,帮助你先有个整体性的了解,也能更深入地学习后面的细节知识。

统计信息(Statistics Class) 描述了表中数据的详细信息,包含表的行数和每一列的数据分布:最大/最小值,不同值的个数(NDV),NULL 值个数和列的平均大小(Average Row Size)。 因此整体流程可以大致分为两部分,分别是统计信息的收集和统计信息的读取计算

下图描述了统计信息的收集和读取计算的整体流程:

统计信息的收集包括手动和定期任务这两种触发方式,对应了图中的两种 Statement :CreateAnlyzeJobStmt(创建 Analyze 定期任务)和 AnalyzStmt(手动执行 Analyze 命令)。两种方式都会创建一个 AnalyzeJob,由它负责具体的统计信息的收集,收集的类型包含全量(FULL)和抽样(SAMPLE)两种。

而收集到的统计信息会存储在 BE 的 _statistics_.table_statistic_v1 表中。其中记录了不同 table 每一列的统计信息,如下图所示:

​读取的整体流程比较简单,Optimzier 会首先从 CachedStatisticStorage 中读取,如果 cache 中没有对应的统计信息,则会从 table_statistic_v1 表中读取对应列的信息。

Optimizer 对统计信息的计算过程比较复杂,接下来将会详细介绍。但大体流程就是利用统计信息计算每个执行计划的 cost,从而从中选择出最优的执行计划。

3.代码导读

接下来我们就通过下文的介绍,来进一步理解 StarRocks 中统计信息的收集、计算以及 Cost 的计算、裁剪过程,并最终找到 Cost 最小的 Plan。

统计信息和 cost 的相关代码分别在 sql/optimizer/statistics 和 sql/optimizer/cost 的目录下,供你查看与参考。

3.1 统计信息的收集

统计信息的收集的流程并不复杂,首先看之前提到的两种 Statement:

  • CreateAnalyzeStmt 通过 StatisticAutoCollector 周期性地调度 AnalyzeJob,周期间隔由statistic_collect_interval_sec 决定。

  • AnalyzeStmt 会立即触发一次 AnalyzeJob 的执行,且只执行一次。

还需注意的是,StatisticAutoCollector 不仅包含用户通过 CreateAnalyzeStmt 创建的 AnalyzeJob,还包括为所有表创建一个默认的抽样任务,用于定期更新统计信息。

如下图所示,我们细化了上一小节整体流程图中的 AnalyzeJob 任务。AnalyzeJob 在执行具体的收集任务时,首先会创建多个 TableCollect Job。而每个 TableCollect Job 又会负责收集对应 Table 的统计信息,收集过程中还会使用 StatisticsExecutor 来负责实际的统计信息的写入。如下图所示:

​这里要注意的是,StatisticsExecutor 可以简单理解为生成对应表的 Insert into select 语句并执行,下面是生成 SQL 语句时的模板。执行该语句时,会将对应的 Table 的列统计信息记录在 table_statistic_v1 表中:

private static final String INSERT_STATISTIC_TEMPLATE = "INSERT INTO " + Constants.StatisticsTableName;

private static final String INSERT_SELECT_FULL_TEMPLATE =
        "SELECT $tableId, '$columnName', $dbId, '$tableName', '$dbName', COUNT(1), "
                + "$dataSize, $countDistinctFunction, $countNullFunction, $maxFunction, $minFunction, NOW() "
                + "FROM $tableName";

3.2 统计信息的计算

Optimizer 的统计信息计算是在 Memo Optimize 阶段计算的,Memo 中 Group 的优化主要依靠不同task 的调度,统计信息的计算则依赖于 DeriveStatsTask。这个 task 的执行流程可以参考 ORCA【1】的统计信息获取过程,下图描述了 T1 join T2 on a=b 时的获取过程:

可以看出,整个过程是自顶向下来请求统计信息:先计算孩子节点的统计信息,然后结合孩子节点的统计信息,自底向上计算 Parent 节点的统计信息,并将计算结果记录在自身的 Group 中。

DeriveStatsTask 任务会使用 StatisticsCalculator 计算不同节点的统计信息,例如下图,就是计算如下 SQL 的统计信息过程:

Select count(1) from t0 group by k1; 

自上而下的请求统计信息,再自底向上的计算每个 Group 的统计信息,大致流程如下:

  • 首先,OlapScanNode 的列统计信息需要 StatisticsCalculator 访问 CacheStatisticsStorage 获取,如果该 cache 中不包含,则从 table_statistic_v1 获取;

  • 其次,OlapScanNode 的统计信息包含 t0 表的行数与 k1 列;

  • 最后,Aggregation 节点需要在 StatisticsCalculator 中计算聚合后的 OutputRowCount,以及 k1 和count(1) 的列统计信息,其中 OutputRowCount 等于 k1 列的 NDV。

可以看到,StatisticsCalculator 中包含所有节点的统计信息的计算方式,该类中表达式统计信息使用 ExpressionStatisticCalculator 计算,对过滤条件的估计则使用 PredicateStatisticsCalculator 计算。

3.3 Cost 的计算

3.3.1 Cost 计算公式

通过上述计算,我们完成了每一个 GroupExpression 的统计信息。在 EnforceAndCostTask 中,我们则需要计算每个 GroupExpression 的 Cost,从而计算出当前 Group 代价最低的 GroupExpression,并记录在 Group 中。

我们可以在 CostModel 中完成不同节点的 Cost 的计算,Cost 的计算公式比较简单:

CalculateCost = CpuCost * cpuCostWeight + MemoryCost * memoryCostWeight + NetworkCost * networkCostWeight

其中 CpuCost、MemoryCost 和 NetworkCost 目前使用 Statistics computeSize 计算。需要说明的是,我们可以把 Statistics computeSize 简单理解为该节点需要处理的数据量(行数 * 列大小),需要处理的数据量越大,节点的CPU开销、内存占用和数据 shuffle 的代价越大。

Statistics ComputeSize = Output RowCount * sum(column_size)

不同节点的 CpuCost、MemoryCost 和 NetworkCost 都是不同的,这主要需要考虑节点的实际开销代价。比如 OlapScanNode 需要计算 CpuCost,而无需计算 NetworkCost,但是 Exchange节点(在Memo 里叫做 PhysicalDistribution)则主要需要计算 NetworkCost。

由于 CPU 计算和内存占用,网络数据传输的实际开销差距很大,这就需要我们通过 cpuCostWeight、memoryCostWeight networkCostWeight 来调整 Cost 结果,尽可能地和实际代价开销相似。

这里需要补充的是,Optimizer 计算的 Cost 结果,只是一个相对值,而非绝对值。因为它是通过比较 Cost 值从 Memo 中抽取出 Cost 最小的执行计划,并不代表实际执行开销。一个好的 Cost Model,是执行计划的 Cost 排序尽量和实际执行开销的排序相同【2】。

3.3.2 GroupExpression Cost 计算

介绍完了每个节点自身该如何计算 cost,接下来我们就开始介绍 Optimizer 是如何使用 Memo 计算 cost,并记录 cost 最低的 GroupExpression 的。需要预先说明一下,接下来的介绍中不会提及 Property 概念,主要是帮助你更容易理解。

不过在详细介绍前,我还需要介绍一下 Cascades 优化框架的基本原则,它利用了 DP 的思想,选出来的最优计划中每个子部分(sub-plan)都是最优的,每个 Group 只需要记录 cost 最小的GroupExpression,计算过的 group 无需重复计算。

上图是表示的是 A、B、C 三表 join 时的 Memo 中的 Group,每个 Group 中都包含 LogicalGroupExpression 和 PhysicalGroupExpression,我们也将使用该图介绍 GroupExpression 的 Cost 计算。

例如 Group[AB] 的 LogicalExpression 包含 [A]⨝[B]和[B]⨝[A],这是通过使用 TansformRule JoinCommutativity 进行逻辑等价变换生成的,两个 LogicalExpression [A]⨝[B]和[B]⨝[A] 是逻辑等价的,Optimizer 使用 ImplementRule 将 [A]⨝[B] 和 [B]⨝[A] 转换成对应的 PhysicalGroupExpression [A]⨝HJ[B]和[B]⨝HJ[A],HJ 则表示 HashJoin。

Memo 中会记录所有 Group 的 cost 最小的 GroupExpression,避免重复计算。我们用下面的的表格来表示,方便理解。

Group

Best GroupExpression

Cost

[ABC]

[AB]

[A]⨝HJ[B]

80

[A]

PhysicalScan(A)

10

[B]

PhysicalScan(B)

20

[C]

PhysicalScan(C)

5

每个节点会计算自身的 Cost,一个 GroupExression 的 cost,就是自身节点的 cost 加上孩子节点的cost。

在 EnforceAndCostTask 中,会对每个 PhysicalGroupExpression 计算 cost。当计算上层节点的 cost 时,首先需要计算出孩子节点的 cost。先计算出 group[A] 和 Group[B] 的 cost,是因为只有一个PhysicalGroupExpression,那么 Best GroupExpression 和对应的 cost 也就很好确定了。

Group[AB] 包含 [A]⨝HJ[B]和[B]⨝HJ[A],需要分别计算其 cost,并记录这 Memo 中。这里需要注意,在计算 [A]⨝HJ[B] 时,会计算出 group[A] 和 group[B] 的 cost;在计算 [B]⨝HJ[A] 时,可以直接使用 Memo 中记录的 group[A] 和 group[B] 结果,避免重复计算。

 我们假设 [A]⨝HJ[B] 的 cost 更小,从而可以将其计算出的 cost 值 :50(自身节点 cost ) + 10(左孩子 cost )+20(右孩子 cost )记录在 Memo 中。group[c] 和 A、B 同理,不再叙述。

Group[ABC] 找到 cost 最低的 GroupExpression 时,还需探索更多的 Group,后面我们会结合 Cost 裁剪进行更详细的介绍。

3.4 Cost 裁剪

在优化器中,对于复杂 SQL 的 plan 搜索空间会很巨大,而 cost 裁剪可以有效地对搜索空间进行裁剪,提升优化器搜索 plan 的性能。

基于 Cascades 的优化器可以自顶向下地探索 plan 空间,使用 Upper bound 对 cost 进行裁剪,该方法在 columbia【3】中叫做 Simple Pruning,从而避免对无效的 GroupExpression 进行探索。

上图接着3.3中介绍的示例,描述了多个 Group 的 Cost 计算和 Cost 裁剪过程。

在计算 Group[ABC] 时,之前的 GroupExression [AB] ⨝HJ[C] 已经计算出其 cost 为20(自身)+80+5 (左右孩子)。其实到这里,我们已经得到了一个可以执行的 Physical Plan 及其 Cost,那么可以使用这个 cost 值,作为后续探索的 Upper Bound。超过这一值时可以直接结束 task,不再进行探索。

而 Upper Bound 的值也可以进行更新,例如 [BC] ⨝HJ[A] 的 cost 值小于 Upper Bound,那么我们将使用更小的 cost 值作为 Upper Bound。

在探索 Group[BC] 时,将使用之前 [AB] ⨝HJ[C] 计算出的 Upper Bound - [BC] ⨝HJ[A] join 节点的 cost,作为孩子节点 Group[BC] 的 UpperBound。假如 [BC] ⨝HJ[A] 经过探索得到了更小的 cost,更新 Uppper Bound,并在探索 Group[AC] 时将使用更小的Upper Bound 70。

假如 Group[AC] 的 join 节点类型是 Cross Join,其 cost 值为200,大于上层节点要求的 Upper Bound,则可以直接结束 Group[AC] 的 EnfocceAndCostTask,而且上层 GroupExpression [AC] ⨝HJ[B] 的 Task 也可以直接结束,无需探索 Group[B]。

最后,Group[ABC] 只记录 cost 值最小的 [BC] ⨝HJ[A],其中 group[AC] 因为被 Upper Bound 裁剪了,没有记录 Best GroupExresssion,因此这里就用 null 表示。如下表所示:

Group

Best GroupExpression

Cost

[ABC]

[BC] ⨝HJ[A]

95

[AB]

[A]⨝HJ[B]

80

[BC]

[B]⨝HJ[C]

55

[AC]

null

null

[A]

PhysicalScan(A)

10

[B]

PhysicalScan(B)

20

[C]

PhysicalScan(C)

5

当 Root Group 完成了探索时,整个 Memo 优化就结束了。后续 extractBestPlan 的时候就可以依据上面的记录,从 Root Group 开始,抽取出代价最小的 Physical Plan。

4.总结

在本文我们介绍了如何通过 Analyze 任务来收集统计信息,如何自底向上地计算各个节点的统计信息,也介绍了 Optimizer 是如何使用 Memo 计算 Cost,以及是如何在 Plan 的探索过程中利用 Cost Upper Bound 进行裁剪,并最终选择出代价最小的 Plan。

参考资料

【1】Soliman M A, Antova L, Raghavan V, et al. Orca: a modular query optimizer architecture for big data[C]//Proceedings of the 2014 ACM SIGMOD international conference on Management of data. 2014: 337-348.

【2】Gu Z, Soliman M A, Waas F M. Testing the accuracy of query optimizers[C]//Proceedings of the Fifth International Workshop on Testing Database Systems. 2012: 1-6.

【3】Xu Y. Efficiency in the Columbia database query optimizer[D]. Portland State University, 1998.

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/118422.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql搭建主从复制

Mysql主从复制搭建过程: 主从需同步时间,主开启ntpd(ntp网络时间协议,它的端口号udp123)服务-----修改配置,从通过/usr/sbin/ntpdate 主ip(ntpdate包需要提前安装);主:开启中继二进…

整数划分问题(Java递归)

整数划分问题(Java递归) 文章目录整数划分问题(Java递归)0、 问题描述1、递归式2、代码3、参考0、 问题描述 整数划分问题 将正整数n表示成一系列正整数之和:nn1n2…nk,其中n1≥n2≥…≥nk≥1,k…

数字校园建设方案技术建议书

【版权声明】本资料来源网络,仅用于行业知识分享,供个人学习参考,请勿商用。【侵删致歉】如有侵权请联系小编,将在收到信息后第一时间进行删除!完整资料领取见文末,部分资料内容: 1.1 华为数字化…

“设计”小哥转行5G网络优化工程师!从零开始,三个月实现逆风翻盘~

5G网络优化,一个陌生的领域,对于一个毫无经验的小白来说,选择转行必定是需要勇气和决心的。好在,在决定选择5G网络优化的这一段时间里,老师给予了我最大的帮助和支持,包括从授课,到练习&#xf…

【Linux】基础IO(open、文件描述符、缓冲区)

文章目录1、从文件操作开始1.1 文件操作的系统调用接口1.2 文件描述符2、重定向3、缓冲区1、从文件操作开始 在C语言阶段,接触了很多库函数,如fopen、fclose、fread和fwrte,这些函数帮助了程序实现了内存与磁盘的输入输出功能。 不过之前都…

轻松搭建MQTT服务器,开发流程全透明

1、使用场景 MQTT服务器适用场景就不多介绍了,基本上实在IOT圈发光发热,所以说是特定领域的一个服务端软件,我们是用在车联网的环境里,用来发布消息。 2、选型 最早说需要使用mqtt服务器,然后我以为需要自己开发服务…

专利代理机构代理专利流程

代理申请专利流程是怎么样的? (一)咨询 1、 确定发明创造的内容是否属于可以申请专利的内容。 (二)技术交底 1、申请人向专利代理人提供有关发明创造的背景资料或委托检索有关内容; 2、申请人详细介绍发明创造的内容,帮助专利代理人充分理解发明创造…

【windows下nginx反向代理 ip地址和域名(含启动重启和关闭3个脚本)】 1.vue案例必须修改vue.config.js

例子1:代理vue的web服务器 1)去官网下nginx: download 下载windows版本nginx,并写好脚本,总体目录如下: StartNginx.bat // 启动脚本 echo off echo "nginx start !!!" nginx.exe -c conf/nginx.conf pause ReStartNginx.bat // 修改nginx.conf后重启脚…

redis学习笔记(基础)

Redis简介 Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、…

飞腾FT-2000/4处理器+复旦微FPGA+国产操作系统解决方案

XM-1203-FPGA飞腾定制主板 自主可控,国产CPU、BIOS和国产Linux操作系统 性能稳定,FT-2000/4处理器 功能接口多样化,可扩展性强 高度集成,具有丰富的接口和电磁兼容性能. XM-1203-FPGA基于飞腾4 核CPU FT-2000/4的处理器板&#x…

【Javassist】快速入门系列09 当检测到数组创建时用代码块替换

系列文章目录 01 在方法体的开头或结尾插入代码 02 使用Javassist实现方法执行时间统计 03 使用Javassist实现方法异常处理 04 使用Javassist更改整个方法体 05 当有指定方法调用时替换方法调用的内容 06 当有构造方法调用时替换方法调用的内容 07 当检测到字段被访问时使用语…

springboot整合之统一结果返回

特别说明:本次项目整合基于idea进行的,如果使用Eclipse可能操作会略有不同,不过总的来说不影响。 springboot整合之如何选择版本及项目搭建 springboot整合之版本号统一管理 springboot整合mybatis-plusdurid数据库连接池 springboot整合…

树与二叉树(遍历)

一、树 树结构是一种非常重要的非线性数据结构,该结构中的一个数据元素可以有两个或者两个以上的直接后继元素。 1、树的定义 树是由 n(n>0)个有限结点组成一个具有层次关系的集合,当 n0时称为空树,当 n>0时称为非空树。 对于非空树来…

水上交通AIS常见问题解决方案

水上交通AIS常见问题解决方案 AIS无信号的几种情况 情况一:AIS未开启。 解决方法:开启AIS设备。 情况二:AIS开启但MMSI码和证书不一致。 解决方法:船长向直属海事部门申请MMSI码,并请专业人员将正确的MMSI码写入AIS设…

数据结构之堆的实现以及实现堆排序和建堆解决Top K问题

文章目录前言1.堆的相关介绍1.什么是堆2.堆的结构2.堆的相关接口具体实现1.堆的声明和堆的初始化2.堆插入数据和删除数据3.堆的其他函数接口3.堆的实际运用1.建堆算法2.堆的应用之堆排序3.堆解决Top k问题4.总结前言 之前对树的相关知识概念进行了简单介绍,本文将实…

odps-jdbc提交MaxCompute数据源SQL报错HTTP/1.0 is not allowed

概述 最近负责的一款数据产品。有个功能:选择某个数据源,比如阿里云的MaxCompute数据源,然后手写SQL,点击自动生成字段(即获取前置SQL里的查询字段。前置SQL可以有drop then create table动作子句,但是最后…

【PS-选区速成】快速选择工具、魔棒工具、对象选择工具

目录 快速选择工具 1、位置 2、3种模式:新选区、添加到选区、从选区减去 3、画笔的设置参数 画笔大小:识别的范围 硬度:边缘的识别能力 间距:识别的连贯程度 跟【选区工具】配套使用的快捷键 1、按【ALT】减区 2、放大…

数据结构之排序【快速排序和归并排序的非递归代码实现及分析】

引言: 今天因为要写论文,所以现在有点迟了,并且此时是北京时间:2022/12/28/1:41 ,我发现晚睡我真的是专业的,当然睡觉我也是专业的,懂的都懂,现在有点迟加上天大寒,手指不可屈伸&am…

android之View的滑动

其实不管是哪种滑动方式,基本思想都是类似的:当点击事件传递到View时,系统记下触摸点的坐标,手指移动的时候,系统记下移动后的坐标,并计算出偏移量,并通过偏移量来修改View的坐标。 下面我们来…

黑客比程序员高在哪里?

黑客其实和一般的程序员一样,但是他们的关注点不一样。黑客关注的是如何破坏,通过这些有创造性的破坏来获取利益,展现自己的能力。而程序员关注的是如何创造,通过创造来获取利益,展现自己的能力。 就如同一个硬币的两…