GPDB-内核特性-资源组内存管理机制-2
本次介绍资源组内存管理的实现。
1、资源组控制器的创建
资源组控制器由函数ResGroupControlInit创建:主要关注点:资源管理控制器pResGroupControl在共享内存中,hash表和slot池也在共享内存;资源组最多可以建100个,slot池大小为max_connections。
2、资源组控制器初始化
资源组控制器创建后,需要对其进行初始化,比如计算segment总内存等。该功能由InitResGroups完成。堆栈如:InitProcess->InitResManager->InitResGroups。
主要完成计算totalChunks、freeChunks、safeChunksThreshold100和完成CPU相关设置并将已有资源组加载到共享内存。计算方法前章节已有介绍。
3、创建资源组
资源组创建语句为:CREATE RESOURCE GROUP rgroup1 WITH (CPU_RATE_LIMIT=20, MEMORY_LIMIT=25, MEMORY_SHARED_QUOTA=60,MEMORY_SPILL_RATIO=20);
由函数CreateResourceGroup完成创建。资源组的创建流程:
重点关注下如何将资源组加入共享内存中的AllocResGroupEntry函数:
1) 计算group->memExpected:资源组定义改组的总内存;
2) 从pResGroupControl->freeChunks链表中分配内存,有可能比group->memExpected小;
3) 将实际分配的内存chunk分配给slots和共享区,优先slots。slots的总配额为group->memQuotaGranted,共享区总配额为group->memSharedGranted
4、资源组SQL的分发与接收
Master需要将资源组创建SQL的执行计划发送给segment以供在segment上创建资源组。分发函数为CdbDispatchUtilityStatement。
Segment由exec_mpp_query接收到该SQL执行计划后进行反序列化解析并执行。
5、资源组信息的分发与接收
开启一个事务时,会将其分配到资源组中。由此可以控制资源组内并发数。这个动作在master上控制。
StartTransaction
if (ShouldAssignResGroupOnMaster())
AssignResGroupOnMaster();
Master上开启事务,开启事务时分配资源组。若gp_resource_group_bypass开启或者是SET、RESET、SHOW命令则资源组为bypass模式,内存的限制为30MB。其他SQL则走上图中蓝色框内的分支:从资源组的空闲链表中找一个空闲的slot;若超出并发数或者没有空闲slot了,则将该进程加入等待队列,直到gp_resource_group_queuing_timeout超时退出,或者被唤醒。被唤醒时要么将其从资源组等待队列中删除,要么该进程上的slot没有等待时将其释放。
开启事务,分配资源组后,在执行器执行时ExecutorStart会将该执行计划分发给segment。这就需要将执行计划序列化以便发送。
序列化执行计划时也会将资源组信息带进去,由函数SerializeResGroupInfo函数完成。QD上以bypass模式通过bypassedSlot.groupId分发资源组ID。
Segment上接收该执行计划,并将资源组信息反序列化出来。由函数SwitchResGroupOnSegment完成。
3、资源组内存如何限制
资源组下,申请内存同样是gp_malloc函数申请,也就是内存上下文中申请。
当需要申请新的chunk时,需要判断下是否达到了红线,达到红线后先清理下再申请。红线即pResGroupControl->safeChunksThreshold100。
申请块:VmemTracker_ReserveVmemChunks->ResGroupReserveMemory:
申请内存的顺序在函数groupIncMemUsage中:
5、总结
这里介绍了资源组内存分配如何执行,包括两种分配模式:bypass模式和资源组分配模式。尤其需要注意bypass模式,QE上它的内存分配限制仅10MB,QD上分配限制是30MB。Bypass模式仅适用于SET、RESET、SHOW语句,开始事务时分配资源组,然后将资源组信息分发到QE。可以看到同一个事务中的SQL语句都使用同一个资源组。