内存分配 Allocstall 导致 SQL 抖动的分析
作者: weiyinghua 原文来源:https://tidb.net/blog/e3d54e6c
一、问题现象
2024 年 9 月 9 日 19:20 左右出现某 SQL 单表索引查询,索引选择性高且返回行数只有几行,该 SQL 大部分时间执行只要几毫秒,然而 SQL 偶尔会出现 1~2 秒的抖动,观察抖动 SQL 执行计划 rpc_time 高达 1 秒多。
二、问题分析
集群拓扑是 3PD-3TiDB-12TiKV,共 3 台 64c / 768GB / 4 * nvme 的 x86 2numa 物理机,每台分别部署 1PD-1TiDB-4TiKV。三大组件 TiDB、TiKV、PD 无瓶颈,且物理机 CPU、IO 资源使用率低,网络无波动与异常。观察到在 2024 年 9 月 9 日 19:20 左右,TiDB - KV Request -RPC Layer Latency
监控有异常,偶尔会出现出现 1~2 秒的抖动,监控含义是:
RPC Layer Latency
= KV Requst Duration
- 在 TiKV 真实执行时间(TiKV req wall time),说明 TiDB Server 到 TiKV 中间链路或 TiDB 的 TiKV client 存在抖动:
同时观察到 TiDB Server 有分配和释放较多内存的 SQL 语句:
三、分析操作系统内存
经分析三大组件 TiDB、TiKV、PD 无瓶颈,且 CPU、网络、IO 正常,接下来分析操作系统内存。检查内存时发现当操作系统 free 内存仅剩约 1GB :
再进一步看 Node_exporter - Vmstat-Page - Allocstall 监控,三台物理机都出现分配内存卡顿(allocstall),以下监控取自 Linux 虚拟文件系统 /proc/vmstat 中的 allocstall,代表发生分配内存卡顿(allocstall)的次数:
为什么 free 内存非常少?
文件缓存介绍
在 Linux 系统中,内存使用情况可以分为几类,包括 used
、free
、buffers
和 cached
等。对于部署了 TiKV 的服务器上 Cached
内存较大(200GB)是正常行为,是因为 TiKV 底层存储引擎使用了buffered IO
模型,后续会介绍 。
Cached
表示被用于文件缓存的内存大小(page cache
)。操作系统会尽可能地利用空闲内存作为文件缓存,以提高系统性能。当访问文件或进行文件操作时,系统会将这些文件数据缓存在内存中,下次访问相同文件时,直接从内存读取,而不是从磁盘读取,提升了访问速度。
Linux 的内存管理机制设计为尽可能地使用所有可用内存来提高系统性能。free
命令显示的 Cached
部分实际上是可用内存的一部分,当系统需要时,内核会根据需要释放缓存空间给其它程序使用。Linux 默认并不对 page cache
大小做限制,只能通过其它参数间接地影响其大小。page cache
也作为可用内存的一部分,计算 “真正可用内存” 公式是:
比如free -h
查看内存输出:
真正可用内存大约是 206GB,看起来内存非常充裕。但实际上程序在请求内存时,需请求连续且足够大的内存才没问题。
TiKV 占用大量文件缓存
对于这 3 台数据库专用物理机来说,TiKV 占用的 page cache
最大。TiKV 底层 raft log 和 KV db 读写 IO 模式都是 buffered IO
,IO 读写操作都会使用 page cache
,由 RocksDB 参数use-direct-io-for-flush-and-compaction
控制,默认不使用 Direct IO
。
buffered IO
:数据在读写文件时,先被缓存在内核的 Page Cache
中。所有的文件读写请求首先会从 Page Cache
中进行。如果数据已经在缓存中,读取会非常快。如果写入操作,数据会先写入 Page Cache
中,操作系统将数据异步地刷入磁盘。但会使得 Page Cache
占用较大内存。这种设计有利于数据访问的性能,但可能在内存有限的情况下造成资源争夺和性能瓶颈,尤其是组件混合部署时。
Direct IO
: 直接将数据从用户空间传输到磁盘,绕过 Page Cache
,也就是不利用操作系统的缓存机制。文件的读写操作直接与磁盘进行交互,读取数据时直接从磁盘加载到应用程序的内存,写入时直接写入磁盘。
总的来讲,TiKV 占用了大量文件缓存使 free 内存非常少。
为什么会发生 allocstall:内存碎片
随着时间的推移内存可能会变得碎片化,虽然有足够的可用内存但不连续,这将导致进程无法分配连续的内存出现 allocstall,此时操作系统需等待直接内存回收(Direct Reclaim)和内存碎片整理 (memory compaction)。为应对内存碎片问题,操作系统会进内存行碎片整理 compaction 将正在使用的页面移动到一起创建出较大的连续区域,这个过程暂时阻止其它进程内存分配直到压缩完成,以下是 Node_exporter - Vmstat-Compact - Compact Stall 监控:
何时发生直接内存回收 Direct Reclaim?
大块内存请求
有新的大块内存分配请求,buddy 系统无法提供足够的连续内存时,系统需回收部分内存。
内存低于水位线
如果内存分配器发现空余内存的值低于 min 水位线,内存严重不足,会产生 direct reclaim。由内核参数 vm.min_free_kbytes
设置,当前 3 台物理机设置为 16MB,本案例 free 内存并未低于 16MB,allocstall 和水位线无关。
关于 buddy 系统简介
buddy 系统用于分配连续的内存页,每个 zone 都有各自的 buddy 系统。buddy allocator 将内存分为 2 的幂次方的页,最大阶数为 10(order),例如:
上述包含 3 个 ZONE:DMA,DMA32,Normal。阶数(order): 0 ~ 10,即 buddy 里对应每一阶对应的数量,最大的阶数为 10, 即 1024 个 pages。例如 Normal 行第 3 列表示有 31620 个 2^2 连续内存块可以用。以此类推越是往后的空间越连续,表示连续空间越多,当大连续空间少时说明内存碎片严重。大部分内核操作都在 NORMAL 区进行,这是最重要的一个区域。当内存页分配时,先根据请求页的大小到相应的链表申请。如果在相应的链表上没有内存页,则向更高阶的链表去查找,如果没找到操作系统将进行内存回收和碎片整理。
以下是 SQL 抖动期间 Node_exporter - Memory - Buddy 监控,从 order 1 到 order 4 均存在归零的情况,高阶内存 order 5~10 一直是 0,说明内存碎片严重,操作系统频繁内存回收和碎片整理,从而影响系统性能:
为什么 min_free_kbytes=16MB
引发 allocstall
操作系统默认设置 vm.min_free_kbytes
16 MB, min_free_kbytes
设置得太低,操作系统会倾向于不提前释放内存,文件缓存(page cache
)占用较大导致操作系统内存紧张,可用连续内存非常少。如果此时有大量连续内存分配请求,操作系统无法满足则触发 allocstall。
四、问题根因
在抖动时段运行较多需分配较大内存的 SQL 语句,由于操作系统使用默认设置 vm.min_free_kbytes
只有 16 MB。在混合部署的服务器上,TiKV 占用大量的文件缓存使 free 内存紧张,可用连续内存不足,TiDB Server 申请内存时需等待先释放内存完毕后再分配,出现 allocstall 引起抖动。
五、改善建议
将内核参数 vm.min_free_kbytes
从默认 16MB 变更为 8GB 后(变更不需重启数据库),SQL 抖动问题恢复:
修改 vm.min_free_kbytes
后高阶内存更加充裕,不再发生 allocstall:
建议未合理设置vm.min_free_kbytes
、出现 allocstall 的 TiDB 集群选择低峰期主动设置,设置后进行观察,如果仍出现 allocstall 则应继续调整。vm.min_free_kbytes
会影响内存回收机制,设置得过大会导致可用内存变少,设置得过小可能会引起内存分配延迟。对于vm.min_free_kbytes
设置的建议经验公式:
以 768GB 内存 x86 2numa 服务器为例 = min( 2 * 0.5% * 768 GB, 4 * 4GB ) ≈ 8GB。
官方文档在 2024 年 4 月 26 日补充了建议设置,请亡羊补牢:https://github.com/pingcap/docs-cn/commit/666c3c58e4ceee517238ea4a50ce6bd3a545552b
版权声明: 本文为 InfoQ 作者【TiDB 社区干货传送门】的原创文章。
原文链接:【http://xie.infoq.cn/article/0bd77478ff662164ea634d673】。文章转载请联系作者。
评论