GPDB-内核特性-资源组内存管理机制-2

时间:2021-11-02 00:56:34

GPDB-内核特性-资源组内存管理机制-2

本次介绍资源组内存管理的实现。

1、资源组控制器的创建

资源组控制器由函数ResGroupControlInit创建:主要关注点:资源管理控制器pResGroupControl在共享内存中,hash表和slot池也在共享内存;资源组最多可以建100个,slot池大小为max_connections。

GPDB-内核特性-资源组内存管理机制-2

2、资源组控制器初始化

资源组控制器创建后,需要对其进行初始化,比如计算segment总内存等。该功能由InitResGroups完成。堆栈如:InitProcess->InitResManager->InitResGroups。

GPDB-内核特性-资源组内存管理机制-2

主要完成计算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完成创建。资源组的创建流程:

GPDB-内核特性-资源组内存管理机制-2

重点关注下如何将资源组加入共享内存中的AllocResGroupEntry函数:

1) 计算group->memExpected:资源组定义改组的总内存;

2) 从pResGroupControl->freeChunks链表中分配内存,有可能比group->memExpected小;

3) 将实际分配的内存chunk分配给slots和共享区,优先slots。slots的总配额为group->memQuotaGranted,共享区总配额为group->memSharedGranted

GPDB-内核特性-资源组内存管理机制-2

4、资源组SQL的分发与接收

Master需要将资源组创建SQL的执行计划发送给segment以供在segment上创建资源组。分发函数为CdbDispatchUtilityStatement。

Segment由exec_mpp_query接收到该SQL执行计划后进行反序列化解析并执行。


5、资源组信息的分发与接收

开启一个事务时,会将其分配到资源组中。由此可以控制资源组内并发数。这个动作在master上控制。

StartTransaction

if (ShouldAssignResGroupOnMaster())

AssignResGroupOnMaster();

GPDB-内核特性-资源组内存管理机制-2

Master上开启事务,开启事务时分配资源组。若gp_resource_group_bypass开启或者是SET、RESET、SHOW命令则资源组为bypass模式,内存的限制为30MB。其他SQL则走上图中蓝色框内的分支:从资源组的空闲链表中找一个空闲的slot;若超出并发数或者没有空闲slot了,则将该进程加入等待队列,直到gp_resource_group_queuing_timeout超时退出,或者被唤醒。被唤醒时要么将其从资源组等待队列中删除,要么该进程上的slot没有等待时将其释放。

开启事务,分配资源组后,在执行器执行时ExecutorStart会将该执行计划分发给segment。这就需要将执行计划序列化以便发送。

GPDB-内核特性-资源组内存管理机制-2

序列化执行计划时也会将资源组信息带进去,由函数SerializeResGroupInfo函数完成。QD上以bypass模式通过bypassedSlot.groupId分发资源组ID。

Segment上接收该执行计划,并将资源组信息反序列化出来。由函数SwitchResGroupOnSegment完成。

GPDB-内核特性-资源组内存管理机制-2

3、资源组内存如何限制

资源组下,申请内存同样是gp_malloc函数申请,也就是内存上下文中申请。

GPDB-内核特性-资源组内存管理机制-2

当需要申请新的chunk时,需要判断下是否达到了红线,达到红线后先清理下再申请。红线即pResGroupControl->safeChunksThreshold100。

申请块:VmemTracker_ReserveVmemChunks->ResGroupReserveMemory:

GPDB-内核特性-资源组内存管理机制-2

申请内存的顺序在函数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语句都使用同一个资源组。