GreenPlum数据库网络层——Segment空闲后端进程IdleQE

【GreenPlum数据库网络层——Segment空闲后端进程IdleQE】GreenPlum数据库网络层——Segment空闲后端进程IdleQE
文章图片

QD(Query Dispatcher、查询调度器):Master 节点上负责处理用户查询请求的进程称为 QD(PostgreSQL 中称之为 Backend 进程)。 QD 收到用户发来的 SQL 请求后,进行解析、重写和优化,将优化后的并行计划分发给每个 segment 上执行,并将最终结果返回给用户。此外还负责整个 SQL 语句涉及到的所有的QE进程间的通讯控制和协调,譬如某个 QE 执行时出现错误时,QD 负责收集错误详细信息,并取消所有其他 QEs;如果 LIMIT n 语句已经满足,则中止所有 QE 的执行等。QD 的入口是 exec_simple_query()。
QE(Query Executor、查询执行器):Segment 上负责执行 QD 分发来的查询任务的进程称为 QE。Segment 实例运行的也是一个 PostgreSQL,所以对于 QE 而言,QD 是一个 PostgreSQL 的客户端,它们之间通过 PostgreSQL 标准的libpq 协议进行通讯。对于 QD 而言,QE 是负责执行其查询请求的PostgreSQL Backend进程。通常 QE 执行整个查询的一部分(称为 Slice)。QE 的入口是 exec_mpp_query()。
QD 和 QE 都是 PostgreSQL backend 进程,其执行逻辑非常相似。对于数据操作(DML)语句(数据定义语句的执行逻辑更简单),其核心执行逻辑由 ExecutorStart, ExecutorRun, ExecutorEnd 实现。

  • ExecutorStart 负责执行器的初始化和启动。Greenplum 通过 CdbDispatchPlan 把完整的查询计划发送给每个 Gang 中的每个 QE 进程。Greenplum 有两种发送计划给 QE 的方式:1)异步方式,使用 libpq 的异步 API 以非阻塞方式发送查询计划给QE;2)同步多线程方式:使用 libpq 的同步 API,使用多个线程同时发送查询计划给 QE。GUC gp_connections_per_thread 控制使用线程数量,缺省值为0,表示采用异步方式。Greenplum 从6.0开始去掉了异步方式。
  • ExecutorRun 启动执行器,执行查询树中每个算子的代码,并以火山模型(volcano)风格返回结果元组给客户端。在 QD 上,ExecutorRun 调用 ExecutePlan 处理查询树,该查询树的最下端的节点是一个 Motion 算子。其对应的函数为 ExecMotion,该函数等待来自于各个 QE 的结果。QD 获得来自于 QE 的元组后,执行某些必要操作(譬如排序)然后返回给最终用户。
  • ExecutorEnd 负责执行器的清理工作,包括检查结果,关闭 interconnect 连接等。
E上的ExecutorStart/ExecutorRun/ExecutorEnd函数和单节点的PostgreSQL代码逻辑类似。主要的区别在 QE 执行的是 Greenplum 分布式计划中的一个 slice,因而其查询树的根节点一定是个 Motion 节点。其对应的执行函数为 ExecMotion,该算子从查询树下部获得元组,并根据 Motion 的类型发送给不同的接收方。低一级的 Gang 的QE把 Motion 节点的结果元组发送给上一级 Gang 的QE,最顶层 Gang 的 QE 的 Motion 会把结果元组发送给 QD。Motion 的 Flow 类型确定了数据传输的方式,有两种:广播和重分布。广播方式将数据发送给上一级 Gang的每一个 QE;重分布方式将数据根据重分布键计算其对应的QE处理节点,并发送给该 QE。
QD 和 QE 之间有两种类型的网络连接:
libpq:QD 通过 libpq 与各个QE间传输控制信息,包括发送查询计划、收集错误信息、处理取消操作等。libpq 是 PostgreSQL 的标准协议,Greenplum 对该协议进行了增强,譬如新增了 ‘M’ 消息类型 (QD 使用该消息发送查询计划给 QE)。libpq 是基于 TCP 的。
interconnect:QD 和 QE、QE 和 QE 之间的表元组数据传输通过 interconnect 实现。Greenplum 有两种 interconnect 实现方式,一种基于 TCP,一种基于UDP。缺省方式为 UDP interconnect 连接方式。
这里我们学习QD上缓存的Segment空闲后端进程IdleQE列表,其实就是代表之前使用libpq连接的segment的QE后端。主要目的就是减少QD使用libpq连接QE后端的时间,可以直接使用之前连接过的Segment空闲后端进程IdleQE,只需要确认连接正常就可以重复使用了。
IdleQE
cdbcomponent_allocateIdleQE函数用于分配一个segdb,如果freelist中有空闲的segdb,则返回,否则,初始化一个新的segdb。idle segdbs 已经与 segment 建立了连接,但新的 segdb 尚未建立,调用者需要自己建立连接。函数流程如下所示:
通过cdbcomponent_getComponentInfo获取CdbComponentDatabases中的segment_db_info或entry_db_info中的CdbComponentDatabaseInfo结构体,其就是primary segment或master在QD上获取的信息结构体。
获取CdbComponentDatabaseInfo结构体的freelist中的第一个IdleQE SegmentDatabaseDescriptor,如果缓冲的IdleQE的类型WRITER/READER不是cdbcomponent_allocateIdleQE函数请求的类型,就继续判定freelist下一个SegmentDatabaseDescriptor。
如果获取到符合要求的SegmentDatabaseDescriptor,从freelist中删除该元素,递减numIdleQEs。
如果没有获取到Segment空闲后端进程IdleQE,则调用cdbconn_createSegmentDescriptor函数创建一个新的SegmentDatabaseDescriptor
递增numActiveQEs
SegmentDatabaseDescriptor *cdbcomponent_allocateIdleQE(int contentId, SegmentType segmentType) { SegmentDatabaseDescriptor*segdbDesc = NULL; CdbComponentDatabaseInfo*cdbinfo; ListCell*curItem = NULL; ListCell*nextItem = NULL; ListCell*prevItem = NULL; MemoryContextoldContext; boolisWriter; cdbinfo = cdbcomponent_getComponentInfo(contentId); oldContext = MemoryContextSwitchTo(CdbComponentsContext); /* Always try to pop from the head.Make sure to push them back to head in cdbcomponent_recycleIdleQE(). */ curItem = list_head(cdbinfo->freelist); while (curItem != NULL) { SegmentDatabaseDescriptor *tmp = (SegmentDatabaseDescriptor *)lfirst(curItem); nextItem = lnext(curItem); if ((segmentType == SEGMENTTYPE_EXPLICT_WRITER && !tmp->isWriter) || (segmentType == SEGMENTTYPE_EXPLICT_READER && tmp->isWriter)) { prevItem = curItem; curItem = nextItem; continue; } cdbinfo->freelist = list_delete_cell(cdbinfo->freelist, curItem, prevItem); DECR_COUNT(cdbinfo, numIdleQEs); /* update numIdleQEs */ segdbDesc = tmp; break; }if (!segdbDesc) { /* 1. for entrydb, it's never be writer. * 2. for first QE, it must be a writer. 第一个QE,必须是writer */ isWriter = contentId == -1 ? false: (cdbinfo->numIdleQEs == 0 && cdbinfo->numActiveQEs == 0); segdbDesc = cdbconn_createSegmentDescriptor(cdbinfo, nextQEIdentifer(cdbinfo->cdbs), isWriter); } cdbconn_setQEIdentifier(segdbDesc, -1); INCR_COUNT(cdbinfo, numActiveQEs); MemoryContextSwitchTo(oldContext); return segdbDesc; }

cdbcomponent_recycleIdleQE函数用于回收使用完的IdleQE。首先递减numActiveQEs,如果指定需要摧毁该IdleQE,或者该IdleQE由于某些原因无法重用,则需要进入清理该IdleQE的流程。如果空闲freelist链表的长度大于gp_cached_gang_threshold,则该IdleQE也需要进行清理。重用流程:如果是Writer IdleQE,则需要将其加入freelist头部;如果是Reader IdleQE,在 cdbcomponent_allocateIdleQE() 中,Reader总是从头部弹出,因此要恢复原始顺序,我们必须将它们推回头部,并记住读取器必须放在Writer之后;递增numIdleQEs。摧毁流程:调用cdbconn_termSegmentDescriptor清理libpq连接,如果是Writer IdleQE,调用markCurrentGxactWriterGangLost函数设置MyTmGxactLocal->writerGangLost为true。
void cdbcomponent_recycleIdleQE(SegmentDatabaseDescriptor *segdbDesc, bool forceDestroy) { CdbComponentDatabaseInfo*cdbinfo; MemoryContextoldContext; intmaxLen; boolisWriter; cdbinfo = segdbDesc->segment_database_info; isWriter = segdbDesc->isWriter; DECR_COUNT(cdbinfo, numActiveQEs); /* update num of active QEs */ oldContext = MemoryContextSwitchTo(CdbComponentsContext); if (forceDestroy || !cleanupQE(segdbDesc)) goto destroy_segdb; /* If freelist length exceed gp_cached_gang_threshold, destroy it */ maxLen = segdbDesc->segindex == -1 ? MAX_CACHED_1_GANGS : gp_cached_gang_threshold; if (!isWriter && list_length(cdbinfo->freelist) >= maxLen) goto destroy_segdb; /* Recycle the QE, put it to freelist */ if (isWriter) { /* writer is always the header of freelist */ segdbDesc->segment_database_info->freelist = lcons(segdbDesc, segdbDesc->segment_database_info->freelist); } else { ListCell*lastWriter = NULL; ListCell*cell; /* In cdbcomponent_allocateIdleQE() readers are always popped from the head, so to restore the original order we must pushed them back to the head, and keep in mind readers must be put after the writers. 在 cdbcomponent_allocateIdleQE() 中,reader总是从头部弹出,因此要恢复原始顺序,我们必须将它们推回头部,并记住读取器必须放在写入器之后。 */ for (cell = list_head(segdbDesc->segment_database_info->freelist); cell && ((SegmentDatabaseDescriptor *) lfirst(cell))->isWriter; lastWriter = cell, cell = lnext(cell)) ; if (lastWriter) lappend_cell(segdbDesc->segment_database_info->freelist, lastWriter, segdbDesc); else segdbDesc->segment_database_info->freelist = lcons(segdbDesc, segdbDesc->segment_database_info->freelist); } INCR_COUNT(cdbinfo, numIdleQEs); MemoryContextSwitchTo(oldContext); return; destroy_segdb: cdbconn_termSegmentDescriptor(segdbDesc); if (isWriter) { markCurrentGxactWriterGangLost(); }MemoryContextSwitchTo(oldContext); }

QEIdentifer
CdbComponentDatabases的freeCounterList中保存了已经释放可重用的QEIdentifier,这是一个int类型的值,用于分配给SegmentDatabaseDescriptor结构体中的identifier成员。nextQEIdentifer就是用于分配该QEIdentifier,如果freeCounterList有空闲的QEIdentifier,就使用第一个;如果freeCounterList为空闲的,则递增CdbComponentDatabases的qeCounter并返回。
static int nextQEIdentifer(CdbComponentDatabases *cdbs) { int result; if (!cdbs->freeCounterList) return cdbs->qeCounter++; result = linitial_int(cdbs->freeCounterList); cdbs->freeCounterList = list_delete_first(cdbs->freeCounterList); return result; }

cdbconn_termSegmentDescriptor函数用于清理libpq连接,并将segdbDesc->identifier放回CdbComponentDatabases的freeCounterList中。
void cdbconn_termSegmentDescriptor(SegmentDatabaseDescriptor *segdbDesc) { CdbComponentDatabases *cdbs; cdbs = segdbDesc->segment_database_info->cdbs; /* put qe identifier to free list for reuse */ cdbs->freeCounterList = lappend_int(cdbs->freeCounterList, segdbDesc->identifier); cdbconn_disconnect(segdbDesc); if (segdbDesc->whoami != NULL){ pfree(segdbDesc->whoami); segdbDesc->whoami = NULL; } pfree(segdbDesc); }

GreenPlum数据库网络层——Segment空闲后端进程IdleQE
文章图片

https://blog.csdn.net/gp_comm...

    推荐阅读