Flink SQL 子图复用逻辑分析
子图复用优化是为了找到SQL执行计划中重复的节点,将其复用,避免这部分重复计算的逻辑。先回顾SQL执行的主要流程 parser -> validate -> logical optimize -> physical optimize -> translateToExecNode。
而子图复用的逻辑就是在这个阶段进行的
private[flink] def translateToExecNodeGraph(
optimizedRelNodes: Seq[RelNode],
isCompiled: Boolean): ExecNodeGraph = {
val nonPhysicalRel = optimizedRelNodes.filterNot(_.isInstanceOf[FlinkPhysicalRel])
if (nonPhysicalRel.nonEmpty) {
throw new TableException(
"The expected optimized plan is FlinkPhysicalRel plan, " +
s"actual plan is ${nonPhysicalRel.head.getClass.getSimpleName} plan.")
}
require(optimizedRelNodes.forall(_.isInstanceOf[FlinkPhysicalRel]))
// Rewrite same rel object to different rel objects
// in order to get the correct dag (dag reuse is based on object not digest)
val shuttle = new SameRelObjectShuttle()
val relsWithoutSameObj = optimizedRelNodes.map(_.accept(shuttle))
// reuse subplan
val reusedPlan = SubplanReuser.reuseDuplicatedSubplan(relsWithoutSameObj, tableConfig)
// convert FlinkPhysicalRel DAG to ExecNodeGraph
val generator = new ExecNodeGraphGenerator()
val execGraph = generator.generate(reusedPlan.map(_.asInstanceOf[FlinkPhysicalRel]), isCompiled)
// process the graph
val context = new ProcessorContext(this)
val processors = getExecNodeGraphProcessors
processors.foldLeft(execGraph)((graph, processor) => processor.process(graph, context))
}
可以看到这里首先会校验relNodes都是FlinkPhysicalRel 物理执行计划的节点
require(optimizedRelNodes.forall(_.isInstanceOf[FlinkPhysicalRel]))
SameRelObjectShuttle
/**
* Rewrite same rel object to different rel objects.
*
* <p>e.g.
* {{{
* Join Join
* / \ / \
* Filter1 Filter2 => Filter1 Filter2
* \ / | |
* Scan Scan1 Scan2
* }}}
* After rewrote, Scan1 and Scan2 are different object but have same digest.
*/
class SameRelObjectShuttle extends DefaultRelShuttle {
private val visitedNodes = Sets.newIdentityHashSet[RelNode]()
override def visit(node: RelNode): RelNode = {
val visited = !visitedNodes.add(node)
var change = false
val newInputs = node.getInputs.map {
input =>
val newInput = input.accept(this)
change = change || (input ne newInput)
newInput
}
if (change || visited) {
node.copy(node.getTraitSet, newInputs)
} else {
node
}
}
}
然后进行rel节点重写,RelShuttle的作用就是提供visit的模式根据实现的逻辑来替换树中的某些节点。可以看到这个实现中会将 同一个objec(注意这里保存visitedNodes使用的是identity hash set) 第二次访问时 copy成一个新的对象,但是有相同的digest,这一步的目的是什么呢?
我们往下面看在后续生成ExecNode时, 会创建一个IdentityHashMap 来保存访问过的Rels,所以意思就是真正生成ExecNode时,是和Rels对象一一对应的。
private final Map<FlinkPhysicalRel, ExecNode<?>> visitedRels = new IdentityHashMap();
private ExecNode<?> generate(FlinkPhysicalRel rel, boolean isCompiled) {
ExecNode<?> execNode = visitedRels.get(rel);
if (execNode != null) {
return execNode;
}
if (rel instanceof CommonIntermediateTableScan) {
throw new TableException("Intermediate RelNode can't be converted to ExecNode.");
}
List<ExecNode<?>> inputNodes = new ArrayList<>();
for (RelNode input : rel.getInputs()) {
inputNodes.add(generate((FlinkPhysicalRel) input, isCompiled));
}
execNode = rel.translateToExecNode(isCompiled);
// connects the input nodes
List<ExecEdge> inputEdges = new ArrayList<>(inputNodes.size());
for (ExecNode<?> inputNode : inputNodes) {
inputEdges.add(ExecEdge.builder().source(inputNode).target(execNode).build());
}
execNode.setInputEdges(inputEdges);
visitedRels.put(rel, execNode);
return execNode;
}
看到这里上面将同一个object 拆成两个的目的就更不可理解了,因为本来是一个object的话在这里天然就复用了,但是拆成2个反而就不能复用了。
这里的目的是先将相同的object被重复引用的节点拆开,然后再根据digest相同以及内部规则来决定是否复用。这样就可以有Flink引擎来控制哪些节点是可以合并的。
SubplanReuseContext
在context中通过ReusableSubplanVisitor构造两组映射关系
// mapping a relNode to its digest
private val mapRelToDigest = Maps.newIdentityHashMap[RelNode, String]()
// mapping the digest to RelNodes
private val mapDigestToReusableNodes = new util.HashMap[String, util.List[RelNode]]()
中间的逻辑比较简单就是遍历整棵树,查找是否存在可reusable的节点,怎么判断可reusable呢?
- 同一digest下,挂了多个RelNode节点,那么这一组RelNode是同一语义的,是可以复用的候选
- 节点没有disable reusable
/** Returns true if the given node is reusable disabled */
private def isNodeReusableDisabled(node: RelNode): Boolean = {
node match {
// TableSourceScan node can not be reused if reuse TableSource disabled
case _: FlinkLogicalLegacyTableSourceScan | _: CommonPhysicalLegacyTableSourceScan |
_: FlinkLogicalTableSourceScan | _: CommonPhysicalTableSourceScan =>
!tableSourceReuseEnabled
// Exchange node can not be reused if its input is reusable disabled
case e: Exchange => isNodeReusableDisabled(e.getInput)
// TableFunctionScan and sink can not be reused
case _: TableFunctionScan | _: LegacySink | _: Sink => true
case _ => false
}
}
例如TableFunctionScan就不能被Reuse(这个原因还没理解),或者exchange只有input被reuse时,该节点才能复用
SubplanReuseShuttle
在以上的visit执行完之后以及知道哪些节点是可以复用的了,最后通过一个Shuttle来将可复用的节点进行替换
class SubplanReuseShuttle(context: SubplanReuseContext) extends DefaultRelShuttle {
private val mapDigestToNewNode = new util.HashMap[String, RelNode]()
override def visit(rel: RelNode): RelNode = {
val canReuseOtherNode = context.reuseOtherNode(rel)
val digest = context.getRelDigest(rel)
if (canReuseOtherNode) {
val newNode = mapDigestToNewNode.get(digest)
if (newNode == null) {
throw new TableException("This should not happen")
}
newNode
} else {
val newNode = visitInputs(rel)
mapDigestToNewNode.put(digest, newNode)
newNode
}
}
}
实现的方式就是记录每个digest对应的newNode,当可以复用时,那么直接返回该复用digest对应的RelNode(替换了原先的digest相同,对象不同的RelNode),这样整棵树中可复用的节点又重新合并了。
Flink SQL 子图复用逻辑分析的更多相关文章
- [源码分析] 带你梳理 Flink SQL / Table API内部执行流程
[源码分析] 带你梳理 Flink SQL / Table API内部执行流程 目录 [源码分析] 带你梳理 Flink SQL / Table API内部执行流程 0x00 摘要 0x01 Apac ...
- 从"UDF不应有状态" 切入来剖析Flink SQL代码生成
从"UDF不应有状态" 切入来剖析Flink SQL代码生成 目录 从"UDF不应有状态" 切入来剖析Flink SQL代码生成 0x00 摘要 0x01 概述 ...
- [源码分析]从"UDF不应有状态" 切入来剖析Flink SQL代码生成 (修订版)
[源码分析]从"UDF不应有状态" 切入来剖析Flink SQL代码生成 (修订版) 目录 [源码分析]从"UDF不应有状态" 切入来剖析Flink SQL代码 ...
- KSQL和Flink SQL的比较
Confluent公司于2017年11月宣布KSQL进化到1.0版本,标志着KSQL已经可以被正式用于生产环境.自那时起,整个Kafka发展的重心都偏向于KSQL——这一点可以从Confluent官方 ...
- MyBatis 别名标签 & sql的复用
1.MyBatis 别名标签 如果在映射文件中,大量使用类名比较长,可以在sqlMapConfig.xml声明别名, 在映射文件中可以使用别名缩短配置,注意此配置要放在最前面 sqlMapConfig ...
- Flink SQL与 SQL Parser ,calcite
http://vinoyang.com/2017/06/12/flink-table-sql-source/ Flink Table&Sql 如何结合Apache Calcite http:/ ...
- 使用flink Table &Sql api来构建批量和流式应用(3)Flink Sql 使用
从flink的官方文档,我们知道flink的编程模型分为四层,sql层是最高层的api,Table api是中间层,DataStream/DataSet Api 是核心,stateful Stream ...
- Apache Flink SQL
本篇核心目标是让大家概要了解一个完整的 Apache Flink SQL Job 的组成部分,以及 Apache Flink SQL 所提供的核心算子的语义,最后会应用 TumbleWindow 编写 ...
- OPPO数据中台之基石:基于Flink SQL构建实数据仓库
小结: 1. OPPO数据中台之基石:基于Flink SQL构建实数据仓库 https://mp.weixin.qq.com/s/JsoMgIW6bKEFDGvq_KI6hg 作者 | 张俊编辑 | ...
随机推荐
- 简单到爆——用Python在MP4和GIF间互转,我会了
写在前面的一些P话: 昨天用公众号写文章的时候,遇到个问题.我发现公众号插入视频文件太繁琐,一个很小的视频,作为视频传上去平台还要审核,播放的时候也没gif来的直接.于是想着找个工具将mp4转换成gi ...
- Python爬虫+数据可视化教学:分析猫咪交易数据
猫猫这么可爱 不会有人不喜欢吧: 猫猫真的很可爱,和我女朋友一样可爱~你们可以和女朋友一起养一只可爱猫猫女朋友都有的吧?啊没有的话当我没说-咳咳网上的数据太多.太杂,而且我也不知道哪个网站的数据比较好 ...
- Linux关闭avahi-daemon服务
avahi-daemon是一种Linux操作系统上运行在客户机上实施查找基于网络的Zeroconf service的服务守护进程. 该服务可以为Zeroconf网络实现DNS服务发现及DNS组播规范. ...
- 岭回归和LASSO
0.对于正则罚项的理解 1.岭回归(L2 ridge regression ) 是一种专用于共线性数据分析的有偏估计回归方法,实质上是一种改良的最小二乘估计法,通过放弃最小二乘法的无偏性,以损失部分信 ...
- 利用噪声构建美妙的 CSS 图形
在平时,我非常喜欢利用 CSS 去构建一些有意思的图形. 我们首先来看一个简单的例子.首先,假设我们实现一个 10x10 的格子: 此时,我们可以利用一些随机效果,优化这个图案.譬如,我们给它随机添加 ...
- Kafka启动遇到ERROR Exiting Kafka due to fatal exception (kafka.Kafka$)
------------恢复内容开始------------ Kafka启动遇到ERROR Exiting Kafka due to fatal exception (kafka.Kafka$) 解决 ...
- APISpace 让你快速获取笑话大全
最近公司项目有一个随机展示各类笑话的小需求,想着如果用现成的API就可以大大提高开发效率,在网上的API商店搜索了一番,发现了 APISpace,它里面的 笑话大全API 非常符合我的开发需求. ...
- javaweb 03: jsp
JSP 我的第一个JSP程序: 在WEB-INF目录之外创建一个index.jsp文件,然后这个文件中没有任何内容. 将上面的项目部署之后,启动服务器,打开浏览器,访问以下地址: http://loc ...
- Python下载网易云收藏
提前声明 仅作为个人学习使用,任何版权问题作者概不负责 本文的语言不会且不可能很严谨 博客园的编辑器有点BUG把我搞晕头了,所以本文可能有点鬼畜 前情 不知道各位有几个是对国内大厂的软件设计很满意的? ...
- AtCoder Beginner Contest 260 F - Find 4-cycle
题目传送门:F - Find 4-cycle (atcoder.jp) 题意: 给定一个无向图,其包含了S.T两个独立点集(即S.T内部间的任意两点之间不存在边),再给出图中的M条边(S中的点与T中的 ...