Linux 的内存管理系统对用户来说是透明的。但是,如果您不熟悉其工作原理,您可能会遇到意想不到的性能问题。对于数据库等复杂软件来说尤其如此。
经过深入调查,我们发现 Linux 内存管理功能透明大页面 (THP) 经常会降低数据库性能。在这篇文章中,我将描述 THP 如何导致性能波动、典型症状以及如何在 Linux 系统中禁用透明大页面。
什么是透明大页面
THP 是 Linux 内核的一个重要特性。它将页表条目映射到更大的页面大小以减少页面错误。这提高了转换后备缓冲区 (TLB) 的命中率。TLB 是内存管理单元使用的内存缓存,用于提高从虚拟内存地址到物理内存地址的转换速度。
当应用程序访问的数据是连续的,THP 通常可以提高性能。相反,如果内存访问模式不连续,THP 就无法发挥作用,甚至可能导致系统不稳定。
不幸的是,众所周知,数据库工作负载具有稀疏的内存访问,而不是连续的内存访问。因此,您应该为数据库禁用 THP。
Linux 如何管理内存
为了理解 THP 可能造成的危害,让我们考虑一下 Linux 如何管理其物理内存。
针对不同的架构,Linux内核采用了不同的内存映射方式,其中用户空间采用多级分页映射内存,以节省空间;内核空间采用线性映射,以达到简单、高效的目的。
内核启动时,会将物理页面添加到伙伴系统中,每次用户申请内存时,伙伴系统就会分配所需的页面,当用户释放内存时,伙伴系统就会释放这些页面。
为了适应低速设备和各种负载,Linux将内存页面分为匿名页面和基于文件的页面。对于低速设备,Linux使用page cache来缓存文件。当内存不足时,用户可以使用swap cache和swappiness来指定这两类页面的释放比例。
为了在内存资源不足的情况下尽快响应用户的内存申请,保证系统正常运行,Linux定义了三个水位线:high、low、min。
- 如果未使用的物理内存小于
low或大于min,则当用户申请内存时,页面替换守护进程kswapd会异步释放内存,直到可用物理内存高于high。 - 如果异步内存回收跟不上内存申请,Linux 会触发同步直接回收,此时所有与内存相关的线程都会同步参与释放内存,当有足够内存可用时,线程才开始获取自己申请的内存空间。
直接回收时,如果页面是干净的,同步回收造成的堵塞时间就很短,否则可能会造成几十毫秒的延迟,甚至根据后端设备的不同,有时甚至会达到几秒的延迟。
除了水印之外,还有一种机制也可能导致直接内存回收。有时,一个线程会申请一大段连续的内存页。如果物理内存足够,但碎片化严重,内核就会进行内存压缩。这也可能触发直接内存回收。
综上所述,线程申请内存时,造成延迟的主要原因是直接内存回收和内存压缩。对于数据库等内存访问不是很连续的工作负载,THP 可能会触发这两个任务,从而导致性能波动。
当 THP 导致性能波动时
如果您的系统性能出现波动,您如何确定 THP 是导致波动的原因?我想分享我们发现的与 THP 相关的三种症状。
最典型的症状:sys cpu 上升
根据我们的客户支持经验,THP引起的性能波动最典型的症状是系统CPU利用率急剧上升。
在这种情况下,如果您使用 perf 创建 on-cpu 火焰图,您将看到所有处于可运行状态的服务线程都在执行内存压缩。此外,页面错误异常处理程序为do_huge_pmd_anonymous_page。这意味着当前系统没有 2 MB 的连续物理内存,从而触发直接内存压缩。直接内存压缩非常耗时,因此会导致系统 CPU 利用率过高。
间接症状:sys load 上升
许多内存问题并不像上面描述的那么明显。当系统分配或其他高级内存时,它不会直接执行内存压缩并留下明显的痕迹。相反,它经常将压缩与其他任务混合在一起,例如直接内存回收。
这个过程中涉及到 Direct Reclaim 使得我们的排查变得更加复杂,比如当 Normal Zone 中未使用的物理内存高于 Watermark 时high,系统仍然会不断回收内存,要搞清楚这个问题,我们需要深入挖掘慢内存分配的处理逻辑。
缓慢的内存分配分为四个主要步骤:
- 异步内存压缩
- 直接内存回收
- 直接内存压缩
- 内存不足 (OOM) 收集
每一步之后,系统都会尝试分配内存。如果分配成功,系统将返回已分配的页面并跳过剩余步骤。对于每次分配,内核都会为伙伴系统中的每个顺序提供一个碎片索引,该索引指示分配失败是由于内存不足还是由于内存碎片导致的。
碎片指数与该参数关联/proc/sys/vm/extfrag_threshold,数值越接近1000,表示分配失败与内存碎片关系越大,内核越倾向于进行内存压缩;数值越接近0,表示分配失败与内存不足关系越大,内核越倾向于进行内存回收。
因此,即使未使用内存高于high水位线,系统也可能会频繁回收内存。由于 THP 会消耗高级内存,因此会加剧内存碎片带来的性能波动。
要验证性能波动是否与内存碎片有关:
1.查看每秒直接内存回收操作数。执行sar -B观察pgscand/s。如果该数字连续一段时间大于0,请按照以下步骤排查问题。
2.观察内存碎片指标,执行cat /sys/kernel/debug/extfrag/extfrag_index获取指标**,重点关注order>=3的block的碎片指标,如果接近1000,表示碎片严重,如果接近0,表示内存不足。
3.查看内存碎片情况。执行cat /proc/buddyinfo并cat /proc/pagetypeinfo显示状态。(有关详细信息,请参阅Linux 手册页。)重点关注顺序 >= 3 的页面数。
相比buddyinfo,pagetypeinfo按迁移类型分组显示更详细的信息。伙伴系统通过迁移类型实现反碎片化。注意,如果所有页面的Unmovable分组顺序小于3,则内核slab对象碎片化严重。在这种情况下,您需要使用其他工具来排查问题的具体原因。
4.对于支持伯克利包过滤器(BPF)的内核,例如CentOS 7.6,您还可以使用PingCAP开发的drsnoop或compactsnoop对延迟进行定量分析。
5.(可选)mm_page_alloc_extfrag使用 ftrace 跟踪事件。由于内存碎片,迁移类型会从备份迁移类型中窃取物理页面。
非典型症状:RES 使用异常
有时在 AARCH64 服务器上启动服务时,会占用数十 GB 的物理内存。通过查看文件/proc/pid/smaps可以看到,绝大部分内存都用于 THP。由于 AARCH64 的 CentOS 7 内核将其页面大小设置为 64KB,因此其常驻内存使用量比 x86_64 平台大很多倍。
如何处理 THP
对于未针对连续存储数据进行优化的应用程序或工作负载稀疏的应用程序,启用 THP 和 THP 碎片整理会对长期运行的服务造成不利影响。
在 Linux v4.6 之前,内核不提供THP 碎片整理defer的 或defer + madvise。因此,对于使用 v3.10 内核的 CentOS 7,建议禁用 THP。但是,如果您的应用程序确实需要 THP,我们建议您将 THP 设置为madvise,这将通过 madvise 系统调用分配 THP。否则,将 THP 设置为never是您应用程序的最佳选择。
要禁用 THP:
1.查看当前THP配置:
2.如果值为always,则执行以下命令:
注意,如果你重启服务器,THP 可能会再次开启。你可以把这两个命令写在文件中.service,让 systemd 帮你管理。