Dispatcher(分布式查问散发器)是MPP数据库的外围组件,所有的查问工作都要通过其进行散发,起着沟通用户到协调者(Coordinator,即QD)和执行调度的关键作用。
在这次的直播中,咱们为大家介绍了Dispatcher基本原理和实现机制,并结合实际用例进行了操作演示。以下内容依据直播文字整顿而成。
Slice与Gang的基本概念与分类
传统MPP数据库采纳无共享Shared-Nothing架构来存储数据,节点之间不共享存储和计算资源,须要应用其余节点的数据时通常利用网络重散发。
图1:Greenplum数据库查问示意图
(图片来自 Greenplum: A Hybrid Database for Transactional and Analytical Workloads,SIGMOD '21 ,序号和箭头系本文作者所加)
以Greenplum为例,如图1所示,当用户连贯到Coordinator(协调者节点)进行查问操作时,会通过Dispatcher组件将查问任务分配到不同的Segment,各Segment之间通过Interconnect模块来传输数据。当各节点查问执行实现后,由QD节点对查问后果进行收集和整顿,再回传给用户。
须要留神的是,在查问工作执行时,用户不会和QE产生任何的连贯,所有音讯都是通过QD 来直达传递,这也是MPP 数据库的重要特色。整个过程中,波及到两个重要的概念:
Slice:为了在查问执行期间实现最大的并行度,Greenplum会将查问打算工作划分为Slices。Slice是查问打算中能够独立进行解决的局部。查问打算会为Motion生成Slice,Motion的每一侧都有一个Slice。正是因为Motion算子将查问打算宰割为一个个Slice,上一层Slice对应的过程会读取下一层各个Slice过程播送或重散布操作生成的数据,而后进行计算。
Gang:属于同一个Slice然而运行在不同的Segment上的过程,称之为Gang。在Greenplum中,共有Unallocated、Reader Gang、Writer Gang、Entry Reader、Singleton Reader五种类型的Gang。其中:
Unallocated运行在QD,个别只在Gather Motion将各个QE回传的后果收取并集时才会用到。
Reader Gang和 Writer Gang会常常用到,而且相干的查问打算会很简单。
● 只读的查问仅蕴含 Reader Gang,蕴含写操作的查问才会应用Writer Gang。
● 一些既读又写的查问(例如 Create table as、Update Returning等)可能同时蕴含这两类 Gang。
● 这两类 Gang 都只有 1-Gang 和 N-Gang 的状况(其中N为Segment数量),优化器不会进一步划分出更粗疏的结构。
Entry Reader和Singleton Reader不太常见,通常在解决子查问或者其它须要保证系统中只有一份值的情景下呈现。
MPP数据库分布式的特点,使其存储容量和计算能力冲破了单机的下限,而成为多个Segment的总和。MPP 数据库的存储和计算紧耦合,对某一部分数据的计算应尽量在这一部分数据的存储节点上间接实现,这也使得所有表在创立时都有指定的分布模式,对该表的存储和计算都应依照该模式在集群的参与者节点上散布开来,从而造成“分布式”。
图2:Greenplum查问执行调度示意图(起源同图1)
许多MPP数据库的初学者,常常会遇到一个问题,Slice和Gang是什么关系?
从二者的作用来看,Slice是对Plan的天然切割,Gang是对Slice的天然实现,两者之间是互相对应的。事实上,在内核代码中两者常常会交替应用。如果是优化器,会更多应用Slice;而在执行器、Dispatcher局部的代码就更多应用Gang。咱们能够认为,Slice和Gang是齐全对应的,前者偏重布局,后者偏重执行。
须要特别强调的是,Writer Gang ≠ Writer QE。事实上,QE并不关怀本人是不是Writer Gang,它的行为只与QD连贯时下发的 isWriter 参数相干,这个参数通过libpq协定握手时 Startup 音讯中的 gpqeid 参数下发到QE上,QE会据此设置Gp_is_writer 全局变量,从而管制QE的行为。
在Greenplum中,任何查问都只有一个Writer QE,而只有写查问才会有Writer Gang。因而,咱们时常听到的“任何查问都有一个 Writer Gang”的论断是不谨严的。
查问散发
查问散发通常分为以下四个阶段:
1.创立Gang并初始化链接;
2.散发查问打算并执行;
3.回收Gang;
4.会话断开时,敞开链接并销毁Gang。
在所有查问中,Plan类查问最为简单,它涵盖了其余三类查问的共用办法。接下来,咱们以Plan类查问为例,开展介绍查问散发的实现流程。
Plan是指Select、Insert 这种可由 explain 指令查看执行打算的查问。这里有一个特例,CTAS(create table as)不属于 Plan,而是属于Utility 类别。
Plan类查问散发的入口点是cdbdisp_dispatchX函数,它会调用 AssignGangs 办法,通过两次先序遍历查问打算树,创立所有的Writer Gang,而后再创立其余Gang:
bool AssignWriterGangFirst(ExecSlice *slice, ...) [ if (slice->gangType == GANGTYPE_PRIMARY _WRITER) { slice->primaryGang = AllocateGang(...); return true; } else { ListCell *cell; foreach(cell, slice-›children) { if (AssignWriterGangFirst(cell->element, ...)) return true; } } return false;}
第一次遍历代码示例
bool InventorySliceTree(ExecSlice *slice, ...) { if (slice->gangType == GANGTYPE_UNALLOCATED) { slice->primaryGang = NULL; } else if (!slice->primaryGang) { slice->primaryGang = AllocateGang(...); } ListCell *cell; foreach(cell, slice->children) { InventorySliceTree(cell->element, ...); }}
第二次遍历代码示例
留神: AssignWriterGangFirst有一个“提前终止”的个性,表明查问打算只能有一个Writer Gang。
咱们看到这两段代码都是调用 AllocateGang 函数实现具体一个Gang的初始化,上方的代码只能调用一次,下方的代码能够调用屡次。
创立并初始化Gang
这里引入了另一个概念:Segment Type, 因为AllocateGang函数并不间接基于Gang Type去工作,须要将 Gang Type 转化为 Segment Type。二者之间对应关系如下:
● Writer Gang肯定要求一个Writer QE,即一个 isWriter 为 true 的Gang;
● extended query(libpq 规定的扩大查问协定,即游标 CURSOR)肯定要求一个Reader QE,即isWriter 为false 的 Gang;
● 其余状况(包含 Reader Gang 在内),实用任何节点。
通过以上的对应关系可能提供更好的兼容性,并进一步提高Gang过程的复用率。下一步会调用:cdbgang_createGang_asynccdbcomponent_allocateIdleQE这两个函数。从名字能够看出, Gang的创立是一个异步的过程,会一次创立一个Gang中的所有链接,而后通过轮询期待创立实现。在创立过程中,首先判断 freelist 中回收上来的QE的相容性,如果相容则间接应用;否则异步调配新的Gang。
异步的过程如下所示:
// cdbgang_createGang_asyncint size = list_length(segments);for (i = 0; i < size; i++) { segdbDesc = newGangDefinition->db_descriptors[i]; cdbconn_doConnectStart(segdbDesc, ...); // -> PQconnectStartParams -> PQconnectPoll connStatusDone[i] = false; pollingStatus[i] = PGRES_POLLING_WRITING;}for (;;) { for (i = 0; i < size; i++) { segdbDesc = newGangDefinition->db_descriptors[i]; if (connStatusDone[i]) continue; switch (pollingStatus[i]) { case PGRES_POLLING_OK: ... // -> cdbconn_doConnectComplete connStatusDone[i] = true; continue; case PGRES_POLLING_READING: ... // set poll to wait till ready to read break; case PGRES_POLLING_WRITING: ... // set poll to wait till write won't get blocked break; case PGRES_POLLING_FAILED: ... // throw exception } } nready = poll(fds, nfds, poll_timeout); if (nready > 0) { for (i = 0; i < size; i++) segdbDesc = newGangDefinition->db_descriptors[i]; pollingStatus[i] = PQconnectPoll(segdbDesc->conn); }}
散发查问打算并执行
在这一阶段,以Gang为单位异步散发M类型音讯(QD到QE的音讯为M类型音讯,调用 exec_mpp_query 入口办法,而用户收到QD的音讯则为 Q 类型音讯,调用 exec_simple_query 入口办法)到所有Gang,并再次通过轮询期待散发实现。
回收Gang
回收Gang时,对每个Gang中的所有QE调用 cdbcomponent_recycleIdleQE 办法,将回收的QE放到freelist中。在下次调配时,会尽量应用曾经创立好的链接。这种办法保障了Writer QE永远在freelist的前部,同时因为Writer QE具备良好的相容性,就使得freelist具备“有序”的性质,在调配 Gang(allocateIdleQE)时就无需遍历整个freelist ,晋升了Gang的调配效率。
销毁Gang
销毁Gang时,通过libpq对每个idle QE进行终止握手,并开释状态。
其它类型查问
● Plan:到所有QE;parsed statement
● Command:到Writer QE;raw SQL
● Utility Statement:到Writer QE;parsed statement
● Set Command:到所有QE;raw SQL
把握了Plan类型查问散发机制之后,就能更好地了解其余的三类查问,它们是 {到所有 QE,到Writer QE} 和对 {parsed statement, raw SQL} 两两组合造成的四种后果。这里的parsed statement指通过查问解析和布局后的PlannedStmt。
SharedSnapshot
Postgres有本地快照机制,保障本地过程的一致性,而Greenplum作为一个分布式系统,必须通过分布式快照,来确保跨Segment的全局一致性。
本地快照保障过程本地一致性,分布式快照保障跨Segment一致性,但在MPP数据库中,一个Segment可能有多个过程,这就须要一个新的机制来保障跨过程的Segment本地一致性。
SharedSnapshot作为基于共享内存的快照同步,成为沟通本地快照和分布式快照的一个桥梁。Postgres中的 SnapshotData自带了过程本地一致性,在这根底之上通过SharedSnapshot实现跨过程的Segment本地一致性,最终通过分布式快照DistributedSnapshot ,实现跨Segment的零碎全局一致性,从而向用户提供全局统一的视图。