暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

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

原创 闫宗帅 2023-03-11
582

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

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语句都使用同一个资源组。


「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论