列存索引服务用户广泛、应用场景复杂。用户往往无法对参数进行有效的调整,可能会导致自测结果不理想,或者需要提交工单给内核团队寻求帮助。但内核团队的支持能力有限,不能够保证支持力度。因此,列存索引内存管理的设计目标是能够在默认情况下提供足够好的自适应管理方法,同时也支持通过人工调整参数值来适配各种场景。本文介绍了列存索引物理内存、虚拟内存以及如何控制内存与并发执行相关内容。
物理内存
列存索引执行器的所有内存全部接入到统一的内存分配接口中。该统一接口的功能如下:
统计实际内存使用量
block即将分配内存的查询操作
kill即将分配内存的查询操作
系统从完全自由运行到开始终止查询需经历如下三个阶段:
当系统内存低于
ExecutionMemoryLimit * BlockMemoryThreshold
(现在固定为0.8)时,所有查询自由运行。当系统内存高于
ExecutionMemoryLimit * BlockMemoryThreshold
但小于ExecutionMemoryLimit * KillMemoryThreshold
(现在固定为0.99)时,查询会被逐步block。block期间的内存被平均分成10份,内存没有超过一个区域,就会有对应比例的查询被block。当系统内存高于
ExecutionMemoryLimit * KillMemoryThreshold
时,所有的查询被block,kill线程开始运行,即将分配内存的查询将会被kill。
系统每隔一段时间或内存达到一定水位就会触发block和kill更新。当完成了这些状态的更新之后,系统会检测是否发生了死锁,即所有查询都处于block状态。当发生死锁时,系统会根据当前内存使用量选择一条查询语句使其进入执行状态或者kill状态。
除每秒触发的整体状态变化外,当内存使用跨越某个区域时,block_lwm (block low water mark)
会被设置为当前区域的最小值减500 MB,block_hwm (block high water mark)
会被设置为当前区域的最大值。当内存使用量超过lwm
或hwm
时才会触发下一次的状态变化。
虚拟内存
借助操作系统的虚拟内存概念来描述这个分配过程。当某个查询从本模块分配内存配额时,实际上当前查询还没有占用任何内存,且在整个查询过程中,内存基本上不会超过分配的配额。分配配额类似于一次mmap,当前查询预约了一部分内存的使用权,但有相当比例的内存最终不会被真实申请出来。OOM产生的实际条件是物理内存超过上限而不是虚拟内存。
虚拟内存的计算方法如下:
首先,系统的物理内存大小由规格参数
ExecutionMemoryLimit
决定。物理内存模块会统计当前正在实际使用的内存(Memoryused)。
虚拟内存可以统计当前并发执行的查询分配的内存配额之和(MemoryQuotaAllocated)。
从VirtualMemory
中为每个查询分配内存配额,每个查询分配的内存配额都等于物理内存大小。
VirtualMemory
实际上就是预期Memoryused
为ExecutionMemoryLimit
时的最高可以分配出的内存配额之和,只要这个比例维持稳定,就可以安全地增加虚拟内存,从而提高系统资源的实际使用率。
虚拟内存与并发执行
对各类查询进行测试后,您会发现在整个查询过程中资源的使用不均匀,始终存在串行点与scale不好的问题。系统在不落盘、不kill(因内存不足)的情况下,需要尽可能地提高查询之间的并发执行能力。如下:
当每个查询使用的内存较少且内存资源充足时,虚拟内存变大,系统并发增加,资源使用率增加。
当每个查询使用的内存较多且内存紧张时,虚拟内存较小,系统并发较低,查询落盘较少或不落盘,kill比例较低或者不执行kill操作。
以上两种情况均能带来最佳性能收益。
性能效果
该测试使用Pareto分布生成的Sysbench表来验证内存充足时增加并发带来的性能提升效果。
测试使用的SQL语句如下:
SELECT SUM(id) FROM sbtest1 GROUP BY k ORDER BY k LIMIT 1;
图1 单独执行SQL语句时的内存波动
图2 单独执行SQL语句时的CPU波动
从图1和图2可以看出,无论是CPU还是内存,虽然峰值比较高,但从平均值来看,资源并没有被充分利用。使用虚拟内存之后,列存索引可以根据内存的实际使用情况,对并发数量进行调整。例如,一个查询在执行过程中平均使用了一半的物理内存,那么就可以将并发数量增加为2个,如下图所示:
图3 根据虚拟内存与物理内存增加并发数量
增加并发数量后,理论上可以让不同查询错峰使用资源,使得资源利用率更高,能有效降低scale能力差以及串行点对性能带来的影响,从而减少整体的时延。如下图所示:
图4 扩大并发减少延时
不打开内存管理时的内存波动情况如下图所示。其中,MEM-TOP为使用TOP采集整个mysqld进程的内存使用量,单位为GB。MEM-EXE是列存索引内部的内存统计量,单位为Byte。由于此时的查询无法并发执行,内存波动较大。
图5 未打开内存管理时的内存波动情况
打开内存管理后,列存索引会根据内存使用的实际情况适时增加并发数量,查询之间的内存峰值与波谷可以相互叠加,波动减小,内存使用率提升。同时也带来了性能的提升。打开内存管理后的内存波动情况如下图所示:
图6 打开内存管理时的内存波动情况
并发数量提升后不仅能够提升性能,CPU使用率也会得到提升。通过更充分地利用资源,整体的延时会有一定程度的下降。效果图如下:
图 7 打开内存管理后的平均延时