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

PolarDB IMCI 内存管理

手机用户2895 2023-06-24
415

问题

内存管理主要面需要解决以下三个问题:

  1. 避免 OOM
  2. 减少查询因内存不足而被 kill 的情况
  3. 在以上两点的基础上,提升并发

其他系统的做法

资源队列

GreenPlum 以及一些专门的资源管理系统如 Yarn,Mesos 往往实现了资源队列,即按照一些规则划分出几个队列,队列之间的资源隔离,或者按照一定优先级可以抢占。这种方法看起来能解决阻塞和公平的问题,但是看起来机制过重,不利用资源充分利用,初步看起来很难在一开始就用上,不过可以作为后续迭代的选项。

参数控制

很多系统采用参数控制的方法,设置许多与资源相关的参数,例如最大并发,最大线程数,IO 速度上限等等,系统会根据这些参数机械地使用资源。这些往往需要根据场景进行深度定制,对用户要求较高。对于社区较活跃的系统来说,似乎也可以很好地解决问题。

内存需求报告

https://help.sap.com/viewer/eb3777d5495d46c5b2fa773206bbfb46/2.0.03/en-US/d4a122a7bb57101493e3f5ca08e6b039.html
提供了一种可以测量内存需求的功能,通过测试,可以生成 report,帮助用户选择合适的规格。这个功能似乎可以进一步扩展,直接通过压测把参数等确定好。

Presto 的内存控制方法

Presto 的做法比较特殊,值得单独拿出来说,它将所有内存分配管理起来,在 memory pool 内存不足时,将后续申请者 block,当其他人释放内存时,再唤醒这些等待者。block 的方法存在一个问题是如何确保还有人能释放内存,而不是所有并发线程都 block 在申请内存上。Presto 使用两个逻辑的内存池,reserved pool 和 general pool,如果 reserved pool 为空,则把 general pool 中最大的 query 重新分配到 reserved pool 中,reserved pool 内存用完,就把 query kill 掉;如果 reserved pool 不为空,则直接 kill general pool 中最大的 query.
Presto 还有一项绝技是可撤销内存,可以命令查询通过落盘数据降低当前使用的内存。
Presto 的做法看起来经过仔细设计,很有借鉴意义。

IMCI 的做法

PolarDB IMCI 作为云上提供的服务,服务用户广泛,场景复杂,且用户往往无法对参数进行有效的调整,靠参数的方法可能会导致用户自测不理想,直接流失,或者提工单给内核团队,但同样内核团队的支持能力是有限的,支持力度不够同样会造成用户不满意。
因此资源管理模块的设计目标是能够在默认情况下提供足够好的自适应管理方法,但是也可以通过调参适配各种不同场景。

物理内存

IMCI (执行器部分)中所有内存全部接入到统一的内存分配接口中。
这个统一的接口有三个功能:

  • 统计实际内存使用量
  • block 即将分配内存的查询
  • kill 即将分配内存的查询

系统从完全自由运行到开始 kill 查询分为 3 个阶段

  1. 当系统内存低于 ExecutionMemoryLimit * BlockMemoryThreshold (现在固定为 0.8) 时,所有查询自由运行。
  2. 当系统内存高于 ExecutionMemoryLimit * BlockMemoryThreshold 但小于 ExecutionMemoryLimit * KillMemoryThreshold (现在固定为 0.99) 时,查询会被逐步 block,这中间的内存被平均分成 10 份,内存没超过一个区域,就会有对应比例的查询被 block。
  3. 当系统内存高于 ExecutionMemoryLimit * KillMemoryThreshold 时,所有查询被 block,kill 线程开始运行,一个查询将会被 kill

系统每隔一段时间或是内存达到一定水位就会触发 block 和 kill 状态更新,当完成了这些状态的更新之后,系统会检测是否发生了死锁,即所有查询都处于 block 状态,当发生死锁时会根据当前内存使用量选择一条查询使其进入执行状态或者 kill 状态。
除了每秒触发的整体状态变化,当内存使用跨越某个区域时,block_lwm (block low water mark) 会被设置为当前区域的最小值减 500M,block_hwm (block high water mark) 会被设置为当前区域的最大值。当内存使用越过 lwm 或者 hwm 时才会触发下一次状态变化。

虚拟内存

这里借助操作系统的虚拟内存概念来描述这个分配过程。当某个查询从本模块分配内存配额时,实际上当前查询还没有占用任何内存,且在整个查询过程中,内存基本上不会超过分配的配额。分配配额类似于一次 mmap,当前查询预约了一部分内存的使用权,但有相当比例的内存最终不会被真实申请出来。OOM 产生的实际条件是物理内存超过上限而不是虚拟内存。
虚拟内存的计算方法如下:

  • 首先系统的物理内存大小是规格参数 ExecutionMemoryLimit 决定的
  • 物理内存模块会统计当前正在实际使用的内存 Memoryused
  • 虚拟内存可以统计当前并发执行的查询分得的内存配额之和 MemoryQuotaAllocated




从 VirtualMemory 中为每个查询分配内存配额,每个查询分得的内存配额都等于物理内存大小。
VirtualMemory 实际上就是预期 Memoryused 为 ExecutionMemoryLimit 时的最高可以分配出的内存配额之和,只要这个比例维持稳定,就可以安全地增加虚拟内存,从而提高系统资源的的实际使用率。

内存与并发

我们对各类查询进行测试,发现查询对资源的使用在整个查询过程中是不均匀的,串行点与 scale 不好的问题是始终存在的。系统在不落盘,不 kill (因内存不足) 的情况下,需要尽可能提高查询间并发。
当我们有了虚拟内存的概念,我们就有了更多的信息和依据,可以采取一些行动。

  • 当每个查询使用内存少,内存资源充足时,虚拟内存会变大,系统并发增加,资源使用率增加。
  • 当每个查询使用内存多,内存紧张时,虚拟内存很小,系统并发小,查询落盘少或不落盘,kill 比例低或者不产生 kill

两种情况均能带来场景下的最佳的性能收益。

效果

这里选择的测试使用 pareto 分布生成的 sysbech 表,这样的测试有一定数据倾斜。该测试主要验证内存充足时增加并发带来的性能提升。

select sum(id) from sbtest1 group by k order by k limit 1;

image.png
图1 单独执行内存波动

image.png
图2 单独执行 CPU 波动

从图1图2可以看出,无论 CPU 还是内存,虽然峰值比较高,但从平均来看,资源并没有被充分利用。使用虚拟内存之后,IMCI 可以根据内存的实际使用情况,对并发进行调整,如图3所示,例如一个查询在执行过程中平均使用了一半的物理内存,那么就可以增加并发到 2.

图3 根据虚拟内存与物理内存扩大并发

如图4所示:增加并发之后,理论上可以让不同查询错峰使用资源,资源利用率更高,降低 scale 能力不好以及串行点对性能带来的影响,从而减少整体的延时。

图4 扩大并发->减少延时
我们来实际看一下内存的使用情况,图5是不打开内存管理时的内存波动情况,其中 MEM-TOP 使用 TOP 采集的整个 mysqld 进程的内存使用,使用左边的坐标轴,单位为 GB,MEM-EXE 是 IMCI 内部的内存统计,使用右边的坐标轴,单位为 byte。此时由于查询无法并发执行,内存波动较大。
image.png
图5

如图6所示,当打开内存管理后,IMCI 会根据内存使用的实际情况增加并发,查询之间的内存峰值与波谷可以相互叠加,波动减小,内存使用率提升,同时也带来了性能的提升。
image.png
图6
性能提升的原因是并发增加后,CPU 使用率也会有一定的提升,通过更充分地利用资源,整体的延时也会有一定程度下降,效果如图7:
image.png
图7

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

评论