写点什么

内存分配 Allocstall 导致 SQL 抖动的分析

  • 2024-11-29
    北京
  • 本文字数:5039 字

    阅读完需:约 17 分钟

作者: weiyinghua 原文来源:https://tidb.net/blog/e3d54e6c

一、问题现象

2024 年 9 月 9 日 19:20 左右出现某 SQL 单表索引查询,索引选择性高且返回行数只有几行,该 SQL 大部分时间执行只要几毫秒,然而 SQL 偶尔会出现 1~2 秒的抖动,观察抖动 SQL 执行计划 rpc_time 高达 1 秒多。


SELECT                cust_name,    cust_uid     FROM         bbc_cx_orgm   WHERE         bbc_id= ?      ORDER BY grg_cd DESC
Sort_5 root 145285.54 bbc.bbc_cx_orgm.grg_cd:desc 2 time:1.25s, loops:2 179.8 KB 0 Bytes└─Selection_15 root 145285.54 eq(bbc.bbc_cx_orgm.bbc_id, "1") 2 time:1.25s, loops:2 179.8 KB N/A └─IndexLookUp_14 root 145285.54 2 time:1.25s, loops:3, index_task: {total_time: 1.24ms, fetch_handle: 1.23ms, build: 1.52µs, wait: 9.52µs}, table_task: {total_time: 1.25s, num: 1, concurrency: 5}, next: {wait_index: 1.35ms, wait_table_lookup_build: 82.8µs, wait_table_lookup_resp: 1.25s} 187.8 KB N/A ├─IndexRangeScan_12(Build) cop[tikv] 145285.54 table:bbc_cx_orgm, index:idx_bbc_id(bbc_id), range:["1","1"], keep order:false, stats:pseudo 2 time:1.21ms, loops:3, cop_task: {num: 1, max: 1.07ms, proc_keys: 2, rpc_num: 1, rpc_time: 1.06ms, copr_cache_hit_ratio: 0.00, distsql_concurrency: 20}, tikv_task:{time:0s, loops:1}, scan_detail: {total_process_keys: 2, total_process_keys_size: 222, total_keys: 3, get_snapshot_time: 25.9µs, rocksdb: {key_skipped_count: 2, block: {cache_hit_count: 11, read_count: 1, read_byte: 23.9 KB, read_time: 14.2µs}}} N/A N/A └─TableRowIDScan_13(Probe) cop[tikv] 145285.54 table:bbc_cx_orgm, keep order:false, stats:pseudo 2 time:1.25s, loops:2, cop_task: {num: 2, max: 1.25s, min: 1.41ms, avg: 624.6ms, p95: 1.25s, max_proc_keys: 1, p95_proc_keys: 1, rpc_num: 2, rpc_time: 1.25s, copr_cache_hit_ratio: 0.00, distsql_concurrency: 20}, tikv_task:{proc max:1ms, min:1ms, avg: 1ms, p80:1ms, p95:1ms, iters:2, tasks:2}, scan_detail: {total_process_keys: 2, total_process_keys_size: 452, total_keys: 4, get_snapshot_time: 616.3µs, rocksdb: {key_skipped_count: 2, block: {cache_hit_count: 31, read_count: 2, read_byte: 27.6 KB, read_time: 323.8µs}}} N/A N/A
复制代码

二、问题分析

集群拓扑是 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 系统中,内存使用情况可以分为几类,包括 usedfreebufferscached 等。对于部署了 TiKV 的服务器上 Cached 内存较大(200GB)是正常行为,是因为 TiKV 底层存储引擎使用了buffered IO模型,后续会介绍 。


Cached 表示被用于文件缓存的内存大小(page cache)。操作系统会尽可能地利用空闲内存作为文件缓存,以提高系统性能。当访问文件或进行文件操作时,系统会将这些文件数据缓存在内存中,下次访问相同文件时,直接从内存读取,而不是从磁盘读取,提升了访问速度。


Linux 的内存管理机制设计为尽可能地使用所有可用内存来提高系统性能。free 命令显示的 Cached 部分实际上是可用内存的一部分,当系统需要时,内核会根据需要释放缓存空间给其它程序使用。Linux 默认并不对 page cache 大小做限制,只能通过其它参数间接地影响其大小。page cache也作为可用内存的一部分,计算 “真正可用内存” 公式是:


可用内存 = Free + Buffers + Cached
复制代码


比如free -h 查看内存输出:


              total        used        free      shared  buff/cache   availableMem:           768G        538G         1G        512M        204G         206G
复制代码


真正可用内存大约是 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),例如:


cat /proc/buddyinfo Node 0, zone      DMA      0      0      0      1      2      1      1      0      1      1      3 Node 0, zone    DMA32      7      6      5      6      5      6      7      7      6      2    272 Node 0, zone   Normal 317681  38869  31620  19250   8931   2579    815    182     19      5      0 
复制代码


上述包含 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设置的建议经验公式:


= min( number of NUMA node * 0.5% * total memory, number of TiKV node * 4GB )
复制代码


以 768GB 内存 x86 2numa 服务器为例 = min( 2 * 0.5% * 768 GB, 4 * 4GB ) ≈ 8GB。


官方文档在 2024 年 4 月 26 日补充了建议设置,请亡羊补牢:https://github.com/pingcap/docs-cn/commit/666c3c58e4ceee517238ea4a50ce6bd3a545552b


发布于: 刚刚阅读数: 4
用户头像

TiDB 社区官网:https://tidb.net/ 2021-12-15 加入

TiDB 社区干货传送门是由 TiDB 社区中布道师组委会自发组织的 TiDB 社区优质内容对外宣布的栏目,旨在加深 TiDBer 之间的交流和学习。一起构建有爱、互助、共创共建的 TiDB 社区 https://tidb.net/

评论

发布
暂无评论
内存分配 Allocstall 导致 SQL 抖动的分析_管理与运维_TiDB 社区干货传送门_InfoQ写作社区