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中:
static int32 groupIncMemUsage(ResGroupData *group, ResGroupSlotData *slot, int32 chunks) { int32 slotMemUsage; /* 当前slot已使用chunk数*/ int32 sharedMemUsage; /* the total shared memory usage, sum of group share and global share */ int32 globalOveruse = 0; /* the total over used chunks of global share*/ /* slotMemUsage = slot->memUsage + chunks */ slotMemUsage = pg_atomic_add_fetch_u32((pg_atomic_uint32 *) &slot->memUsage,chunks); /* sharedMemUsage >0:slot配额不够分配 */ sharedMemUsage = slotMemUsage - slot->memQuota; if (sharedMemUsage > 0){ /* share区分配数 */ int32 deltaSharedMemUsage = Min(sharedMemUsage, chunks); /* oldSharedUsage = group->memSharedUsage * group->memSharedUsage+=deltaSharedMemUsage */ int32 oldSharedUsage = pg_atomic_fetch_add_u32((pg_atomic_uint32 *) &group->memSharedUsage, deltaSharedMemUsage); /* 共享区空闲chunk数 */ int32 oldSharedFree = Max(0, group->memSharedGranted - oldSharedUsage); /* 超出共享区分配数*/ int32 deltaGlobalSharedMemUsage = Max(0, deltaSharedMemUsage - oldSharedFree); /* freeChunks -= deltaGlobalSharedMemUsage 全局共享区超出分配数 */ int32 newFreeChunks = pg_atomic_sub_fetch_u32(&pResGroupControl->freeChunks, deltaGlobalSharedMemUsage); /* globalOveruse >0:超出共享区分配数 */ globalOveruse = Max(0, 0 - newFreeChunks); } /*分组已使用chunk数,不论在哪部分分配 */ pg_atomic_add_fetch_u32((pg_atomic_uint32 *) &group->memUsage,chunks); return globalOveruse; }
5、总结
这里介绍了资源组内存分配如何执行,包括两种分配模式:bypass模式和资源组分配模式。尤其需要注意bypass模式,QE上它的内存分配限制仅10MB,QD上分配限制是30MB。Bypass模式仅适用于SET、RESET、SHOW语句,开始事务时分配资源组,然后将资源组信息分发到QE。可以看到同一个事务中的SQL语句都使用同一个资源组。