《深入浅出Greenplum内核》系列直播以每月一场的速度持续推出中。在第一场《架构解读》直播里,我们了解了Greenplum的整体架构、存储管理、索引、查询执行、事务与日志等内容。今天(5月22日),第二场《Greenplum内核揭秘之执行引擎》也顺利播出啦!现在,我们来回顾一下直播演讲内容吧!
看完别忘了前往askGP做一下小测试(ask.greenplum.cn/exam)巩固一下所学的知识点哦!
感谢大家参加Greenplum的直播活动!在《深入浅出Greenplum内核》系列活动的第二场直播中,我们为大家详细介绍了Greenplum执行器内容,以及Greenplum为了完成执行计划而设计的两个特殊模块Dispatcher和Interconnect等内容。执行器
首先我们先来了解一下什么是执行器。简单来讲,执行器是处理一个由执行计划节点组成的树,并返回查询结果。那么什么是执行计划节点呢?从本质上讲,一个执行计划节点,实际上就是一个数据处理节点。从下图可看到,在数据输入后,执行节点会对数据进行数据处理,然后返回数据作为输出。这些执行节点会被组织成树的形式。
下图是一个SELECT查询的执行计划树。通过优化器优化后,就会生成这样的树状结构,我们可以看到里面有四个执行节点,包括HashJoin节点,Hash节点,顺序扫描节点,所有的节点通过树的方式组织在一起,来表示各节点之间的数据流动或者顺序关系。 每一个计划节点包含足够多的元数据信息提供给执行器。
图中的Seq Scan被称为原发性的扫描节点,原发性的扫描节点是指,节点本身可以自己产生数据,而不依赖于其他节点;反之,非原发性扫描节点是需要子节点来为其提供数据,图中的Hash Join和Hash就是非原发性扫描节点。了解了原发性扫描节点和非原发性扫描节点的不同,就可以更好的理解后面的执行模型。
那么执行器是怎么执行生成的执行计划树呢?就需要利用执行模型了。面对这样的执行计划树时,处理方式其实很多,我们会根据包括每一个节点内的数据输入是怎么样的规定,输出有什么样的特点等不同的信息,会选择不同的执行模型。现在我们来介绍一下几种常见的执行模型。执行模型
第一种是迭代模型,也被称为流式模型,或者是抽拉式模型。它的定义非常简单,每一个执行节点本质上就是一个next函数,我们会从一个树节点的根节点一直往下执行这个next 函数。next 函数的实现会遵循这样的特点:从输出角度看,next 函数的每一次调用,执行节点返回一个tuple,没有更多tuple的时候返回一个NULL。
从输入的角度看,执行节点实现一个循环,每次调用子执行节点的next函数来获取它们的输出,并处理它们直到能返回一个tuple或者NULL。
执行控制流方向是自上往下,不断抽拉的方式,由上层节点直接驱动下层节点来进行数据的驱动。而从数据流的角度来看,还是由上层节点往下层节点传输来完成。
向量化模型
第二种模型就是向量化模型,和迭代模型有一些相似之处,比如每一个执行节点实现一个next函数,但也有其不同之处。每一次迭代,执行节点返回一组tuple而非一个tuple,从而减少迭代次数,可以利用新的硬件特性如SIMD来加快一组tuple的处理。同时一组tuple在不同的节点之间传输,对列存也更加友好。执行节点实现一个循环,每次调用子执行节点的next函数来获取它们的输出,并能够批量的处理数据。执行控制流方向自上而下,采用pull的方式。Push执行模型
第三种模型是目前比较热门的模型——PUSH执行模型。每一个执行节点定义两个函数Produce函数
Consume函数
现在我们通过一个例子来看一下,下图中有三个节点,一个扫描节点,一个投影节点,一个Join 节点。每个节点都生成了两个函数,一个生产函数,一个消费函数。整个PUSH模型是怎么做的呢?图中的红框标注的为原发性的扫描节点,蓝框标注的是非原发性的扫描节点。非原发性的扫描节点中的生产函数并不做真正的生产工作,而更多是承担了控制工作,会调用它的子节点的生产函数。因此投影节点和Join节点会调用scan的生产函数。由于Scan是原发性的,因此会在生产并得到数据后,开始驱动数据的消耗。
PUSH模型是由下层的节点驱动上层的节点来完成的。数据流向也是自下而上的。下层驱动模型可以相对容易的转换成由数据驱动的代码。好处就是,上层的操作就会变成本节点的算子,增加代码的局部性。此外,这样的代码可以更方便进一步转换为一个纯计算代码,例如使用LLVM优化等。个人认为这种模型通用性不强,只能做一些局部的优化。
Greenplum使用的是迭代模型,但我们正在积极探索向量化模型和PUSH模型。Greenplum正在开发相应的功能,并提交到PG社区,基本思路是利用custom scan 的可定制特性,实现向量化版本的AGG节点,SORT节点,并替换原有查询执行树中的相应节点。大家对这一块感兴趣也欢迎去相应的邮件列表查看。
而Greenplum执行器面临了更大的挑战,首先Greenplum是MPP架构,意味着大规模的并行计算,每个执行节点就需要更多的处理过程。同一个执行节点就会变成多个处理过程,而数据也会被拆分。执行节点之间进行输入和输出的过程中,需要不同的计算单元进行交换。
Greenplum执行的挑战和解决方案——Motion
此外,Greenplum是一个Shared-Nothing的架构,这就意味着不同的计算单元之间的输入输出的过程会受阻。
面临这样的挑战,Greenplum的解决方案是加了一个新的名为MOTION的执行节点,用来在不同的执行节点之间移动数据。加了Motion后,执行计划仍然是树状结构。只是在不同的节点之间加了个Motion节点,并最终通过Motion节点,将数据进行汇总。
接着我们来剖析一下并行化Plan。在下面的例子中,我们有一个Master和34个Segment节点。现在有两张表:单身男和单身女,数据分布在不同的SEGMENT上。如果我们要进行一个查询,将这两张表格中,籍贯相同的单身男和单身女进行相亲匹配,我们是如何生成一个可以被并行化执行的计划树呢?
为了更好的说明这个问题,我们可以在现实生活中进行映射,来方便大家理解。如果在现实生活中,我们会怎么办?如果这些不同户籍的单身男女在同一个省,此时处理方法就相对简单,
首先把单身女找出来
再把单身男找出来
再把同户籍的男生女生分配到相同的会场
从而较为快速的把这些单身男女进行匹配和筛选。
如果这些单身男女并不在同一个省,而是分布在全国34个省中,此时要如何处理呢?
为了做一个最优的策略,我们会分情况来看,
1. 如果单身男女都居住在户籍所在地
可以由各省独自举办相亲会
针对本省的单身男女组织相亲
将结果返回总部
对应到Greenplum上,是这样的
2. 对于单身女居住在户籍所在地,而单身男生分散在全国各地。此时采取的策略可以是,
- 各省的分部独自举办相亲会:
- 将每个省的单身男青年找出来,并将他们通过火车派送回原户籍所在地。
- 由每个省接待这些男青年,并在本省找出女单身青年,对他们进行相亲配对。
如果女生数量很少,此时可以采用的策略是
找到本省所有适龄单身女青年,并为其买好34个省份的车票,每个省份都去一趟。
每个省接待这些单身女青年,并安排其与生活在本省的男青年相亲,找出户籍一致的配对。
对应到Greenplum上,是这样的
3. 如果单身男女随机分布在全国各地,此时有两种策略策略1:在总部举办相亲会,各省把单身男女通过火车派送回总部,总部接待并安排相亲配对。但由于总部资源有限,一般都不会采取这种策略;策略2:各分部举办相亲会:
各省找出居住在本省的适龄单身男,并按户籍派送到相应的省。
各省找出居住在本省的适龄单身女,并按户籍派送到相应的省。
各省接待全国归来的男女,进行相亲配对。
在进行相亲策划后,我们得出了以下经验总结:
人多力量大的原则,尽量利有各省的分部
要首先分析当前男女青年的地域分布
必要时使用交通工具来打破地域的限制
其实在Greenplum里,也采用了类似的处理方式。每一张表都会有数据分布信息,Greenplum支持三种分布策略:键值分布(按列分布)、随机分布、复制分布(数据在所有的segment上都保留了一份数据)。
Greenplum内部采用更通用的Locus信息来表示分布信息,所有的数据集合都会有数据分布状态的。
Greenplum通过Motion来打破物理上的隔离。包括下图中的四种Motion。Redistribute Motion是通过键值把Tuple在多个节点间进行重分布。Gather/Gather Merge Motion是把不同Segment上的数据聚集到一个节点上,Gather Merge保证了一个有序的收集过程。Broadcase Motion顾名思义就是广播,每个节点都发送一份。Explict Redistribute Motion常用于Update/Delete操作,该类操作需要在数据原来所在的节点上进行更新或删除,保证数据分布不会出现不一致。gp segment id隐藏列保存了数据所在原来节点信息。
并行化Plan
Motion会引起数据的迁移,带来执行代价,所以Greenplum会对需不需要做Motion进行代价评估,评估依据主要是当前数据集合的数据分布状态和在当前数据集合上将要执行的操作。现在我们通过一个分布式Join的例子来巩固一下。下面是一个简单的inner join。A、B都是按照Hash分布的键值表。也就是数据被分散在各个Segment上,而每个Segment上只有部分数据。要做到A inner join B的完整数据集,就需要把B表全部复制到所有的segment上,和A的部分数据Join。得到的Plan就如下图所示。前面我们提到,在Join完成后,也会有个数据分布。本例中,在Join完成后,还是会通过Hash分布。接着,由于QD会直接和Client进行交互,因此需要把所有的数据Gather到QD上,再由QD发送给Client。而其中的优化过程,会在本《深入浅出Greenplum内核》系列直播后续的课程中细讲,请大家关注。
如果A是一个键值表,B是一个复制表。前面的Broadcast就不需要做了,可以直接进行Join。每个并行处理单元处理下图中的计划树,再Gather到QD即可。
如果A是键值表,而B是general的数据分布。B会在每个segment上都能产生1-10的数据,就能满足Join的需求。如果A不变,而B是一个子查询,是SingleQE的数据分布,即在一个segment上提供这样的数据。其中一种策略就是,把分布各个Segment上的A的数据都Gather到一个Segment上执行。此时Join后的数据模型就会变成SingleQE的数据分布。如果在Inner Join时加个条件,就可以将Broadcast Motion换成Redistribute Motion。让c2这一列按照c1这个Hash重新分布到其他segment上,从而减少数据的移动。
我们再来看一个要AGG操作的例子,在下面的例子中,对A进行AGG操作,计算c1的count值。此时,我们只需要在每个Segment上做AGG,再Gather到QD即可。
如果A表是按照C2做分布的(非两阶段),则前面的策略便不可用了。此时,我们可以将A可以按照C1做Redistrbute Motion,在前面提到的操作即可。
Dispatcher
讲完分布式Plan的产生,我们再来看一下Greenplum中为了支持分布式plan而设计的模块。第一个就是Dispatcher。上面提到的相亲的策略,各省的分部独自举办相亲会。
首先每个省的单身男青年找出来,并将他们通过火车派送回原户籍所在地。
然后每个省接待这些男青年,并在本省找出女单身青年,对他们进行相亲配对。
对应到Greenplum上,有了分布式plan,一堆计算资源是如何分配调度和执行起来的呢?
Dispatcher首先要做的就是分配QE资源。从plan的角度来看,会将计划做成SliceTable,SliceTable中会告知Slice2从34个segment来分配资源,而Slice3只需要Segment2来提供资源即可。Dispatcher从SliceTable中得到信息后,会去分配资源。它会向CdbComponentDatabases这个component来申请资源,并将得到的资源回写到SliceTable中。原本,SliceTable中只包括了需要在哪几个Segment上起QE资源的较模糊的指令,但在分配完后,每个SliceTable就会得到QE资源具体的节点信息,包括地址和端口等。
Dispatcher分配QE资源通过调用allocateGang()函数完成。GANG大小的分配非常灵活,最小可以只分配一个QE资源,而一般为segment的个数,甚至可以支持大于segment的个数的QE资源,即每个segment可以为一个gang分配多于一个的QE资源。此外QE资源闲置后,并不会被马上回收,而是可以被后续的查询重用,减少了重复分配QE带来的开销。
Dispatcher第二个功能是分发任务。CdbDispatchPlan可以分发并行性化plan的任务,SliceTable也会连同这个分布式plan一起发给QE。这样的话所有的QE通过SliceTable可以找到自己预先被分配属于哪个Gang,以及它的父节点的Gang是哪些以便于建立节点间通信。通过Parent Gang具体的QE描述符,我们就可以知道要把数据传送到哪个端口。也可以分发纯文本的、两阶段提交、查询树的任务。
Dispatcher的第三个功能就是协调功能,通过cdbdisp_checkDispatchResult函数来控制QE的状态。有下面四种等待模式。
下图就是一个典型的Dispatcher程序。Greenplum内的代码基本都会遵循这样的逻辑:分配上下文-分配资源-发送任务-等待发送的完成-等待QE的状态-销毁上下文。
Interconnect
第二个模块就是Interconnect。Greenplum是通过网络在QE之间移动数据,这个网络模块就是Interconnect。在Motion节点被初始化时,发送端和接收端就会建立Interconnect网络连接。在Motion节点执行时,就会通过Interconnect来发送数据。下图是Interconnect的分层介绍。从应用层来说,主要任务是发送数据。Interconnect会对Tuple进行包装,将其包装成一个个Chunk。有些Tuple很大,就会进行切割,将其切成多个Chunk。Chunk通过数据包发送给receiver端。应用层还有一些数据流控制的包,包括EOS包,STOP包等。所有的包都会通过系统传输层中的UDPIFC和TCP IC进行传输。
UDPIFC是Greenplum自己实现的一种RUDP(Reliable User Datagram Protocol)协议。基于UDP协议开发的,为了支持传输可靠性,实现了重传,乱序处理,重传处理,不匹配处理,流量控制等功能。GPDB当初引入UDPIFC主要为了解决复杂OLAP查询在大集群中使用连接数过多的问题。UDPIFC实际上是一种线程模型。
后续,我们也可能会增加一些新的Interconnect类型,包括QUIC协议,Proxy协议等,欢迎大家的关注。
关于Hashjoin的内容,由于时间原因,本次分享就不做详细的讲解,如果大家对这一块感兴趣,可以反馈给我们社区,我们可以在后面添加专门的讲解。大家可以参考一下之前Greenplum中文社区公众号发布的关于Hashjoin的文章来了解相关内容。
Greenplum小测试第二期
(ask.greenplum.cn/exam)参加活动即可获得“青梅称号”,截图分享至朋友圈还有机会获得Greenplum变色杯!
我知道你
在看
哦