《深入浅出Greenplum内核》系列直播以每月一场的速度持续推出中。在第一场《架构解读》直播里,我们了解了Greenplum的整体架构、存储管理、索引、查询执行、事务与日志等内容。今天(5月22日),第二场《Greenplum内核揭秘之执行引擎》也顺利播出啦!现在,我们来回顾一下直播演讲内容吧!
看完别忘了前往askGP做一下小测试(ask.greenplum.cn/exam)巩固一下所学的知识点哦!
执行器
首先我们先来了解一下什么是执行器。简单来讲,执行器是处理一个由执行计划节点组成的树,并返回查询结果。那么什么是执行计划节点呢?从本质上讲,一个执行计划节点,实际上就是一个数据处理节点。从下图可看到,在数据输入后,执行节点会对数据进行数据处理,然后返回数据作为输出。这些执行节点会被组织成树的形式。
下图是一个SELECT查询的执行计划树。通过优化器优化后,就会生成这样的树状结构,我们可以看到里面有四个执行节点,包括HashJoin节点,Hash节点,顺序扫描节点,所有的节点通过树的方式组织在一起,来表示各节点之间的数据流动或者顺序关系。 每一个计划节点包含足够多的元数据信息提供给执行器。
图中的Seq Scan被称为原发性的扫描节点,原发性的扫描节点是指,节点本身可以自己产生数据,而不依赖于其他节点;反之,非原发性扫描节点是需要子节点来为其提供数据,图中的Hash Join和Hash就是非原发性扫描节点。了解了原发性扫描节点和非原发性扫描节点的不同,就可以更好的理解后面的执行模型。
现在我们来介绍一下几种常见的执行模型。
执行模型
第一种是迭代模型,也被称为流式模型,或者是抽拉式模型 。它的定义非常简单,每一个执行节点本质上就是一个next函数,我们会从一个树节点的根节点一直往下执行这个next 函数。next 函数的实现会遵循这样的特点:
从输出角度看,next 函数的每一次调用,执行节点返回一个tuple,没有更多tuple的时候返回一个NULL。
从输入的角度看,执行节点实现一个循环,每次调用子执行节点的next函数来获取它们的输出,并处理它们直到能返回一个tuple或者NULL。
执行控制流方向是自上往下,不断抽拉的方式,由上层节点直接驱动下层节点来进行数据的驱动。而从数据流的角度来看,还是由上层节点往下层节点传输来完成。
这种执行模型的有点在于规则简单,易懂,资源使用少,通用性好,大部分的执行计划节点一般都可以用这种模式来实现。缺点也很显而易见,由于每次迭代只返回一个tuple,迭代次数多,代码局部性较差,同时对CPU cacheline也不是很友好。
向量化模型
执行节点实现一个循环,每次调用子执行节点的next函数来获取它们的输出,并能够批量的处理数据。执行控制流方向自上而下,采用pull的方式。
Push执行模型
Produce函数
Produce函数:看起来像是一个执行节点tuple的生产函数,其实不然,对于非自主生产的执行节点,produce函数更像一个控制函数,它不做过多的生产的工作,想反它会立即调用子节点的produce函数。具有自主生产的执行节点(一般为叶子节点),其produce函数名副其实的生产tuple,并驱动父节点的consume函数提取数据。
Consume函数
Consume函数:被下层节点驱动调用,接收子节点数据,进行各种运算,并驱动其父节点的consume函数。
现在我们通过一个例子来看一下,下图中有三个节点,一个扫描节点,一个投影节点,一个Join 节点。每个节点都生成了两个函数,一个生产函数,一个消费函数。整个PUSH模型是怎么做的呢?图中的红框标注的为原发性的扫描节点,蓝框标注的是非原发性的扫描节点。非原发性的扫描节点中的生产函数并不做真正的生产工作,而更多是承担了控制工作,会调用它的子节点的生产函数。因此投影节点和Join节点会调用scan的生产函数。由于Scan是原发性的,因此会在生产并得到数据后,开始驱动数据的消耗。
PUSH模型是由下层的节点驱动上层的节点来完成的。数据流向也是自下而上的。下层驱动模型可以相对容易的转换成由数据驱动的代码。好处就是,上层的操作就会变成本节点的算子,增加代码的局部性。此外,这样的代码可以更方便进一步转换为一个纯计算代码,例如使用LLVM优化等。个人认为这种模型通用性不强,只能做一些局部的优化。
Greenplum使用的是迭代模型,但我们正在积极探索向量化模型和PUSH模型。Greenplum正在开发相应的功能,并提交到PG社区,基本思路是利用custom scan 的可定制特性,实现向量化版本的AGG节点,SORT节点,并替换原有查询执行树中的相应节点。大家对这一块感兴趣也欢迎去相应的邮件列表查看。