ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Linux内核Refault Distance机制详解:内存回收与工作集判定

Linux内核Refault Distance机制详解:内存回收与工作集判定 1. Refault Distance是什么为什么内核内存管理需要它做Linux性能调优和内核存储相关工作的人这两年应该经常在内存管理相关的讨论里撞见Refault Distance这个词。尤其是遇到过page cache大量命中率波动、容器内存回收导致业务抖动、或者btrfs等文件系统在内存压力下表现不正常的情况最后定位问题的时候十有八九会绕回到这个名字上。先说它是什么。Refault Distance中文直译是再失效距离更准确的说法是页面换出后再次因缺页而重新加载的间隔距离。它描述的是一个被回收掉的页在被重新访问之前虚拟地址空间里发生了多少次内存回收活动。这个概念不是简单的内核统计项而是一套判断机制的输入参数。内核根据它来决定当一个页面在换出后再次被访问时到底是应该把它重新放回活跃链表还是认为它本来就不该占着内存继续留在非活跃链表里等下一次回收。它解决的核心问题是内存回收中的误杀和抖动。传统LRU回收策略只按访问新旧程度排序但一个页面被换出之后再次被访问不代表它真的是热数据。如果每次二次访问都激活页面那么工作集比较大的场景下内存会被大量只偶尔碰一下的数据占据真正频繁访问的数据反而被挤出去形成反复缺页、反复回收的抖动循环。Refault Distance的价值就是给内核一把尺子去量这次重新加载的页面到底值不值得为它保留内存。这篇文章适合三类人看一是做内核存储和文件系统开发的人btrfs的缓存在很多场景下直接受益于这套机制二是做容器云原生基础设施、处理内存超卖和混部的运维工程师很多内存抖动和RT抖动问题根源就在refault判定上三是系统级性能优化、对Linux内存管理子系统有兴趣的开发者理解这套逻辑对阅读vmscan.c、workingset.c会顺畅很多。我接下来会先从页面回收这条主链路讲起再到Refault Distance怎么被算出来最后用实际可复现的实验来讲它在什么场景下有效、什么场景下需要人为干预。2. 从页面回收到working setRefault Distance到底量的是什么2.1 内存回收的基本决策困境要理解Refault Distance得先回到内存回收的原始问题。Linux内核的内存回收机制每次在内存紧张时会扫描LRU链表把一些页面换出。每个页面从被访问状态跌落到可以被回收状态在虚拟地址空间里其实经历了一个过程先是CPU访问它内核在页表里设置accessed标志位然后页面在active链表和inactive链表之间挪动最终被清理出来。这里有个巨大的决策困境到底哪些页面该被回收如果回收少了内存压力没缓解如果回收多了把刚被访问过、马上还要用的页面换出去了那就得走一遍磁盘读回性能损失是数量级的。传统的做法是LRU的直觉——访问时间越久远的页面越可能以后用不上优先回收。这在访问模式相对均匀的场景下表现很好但在重度文件缓存场景下很痛苦。比如数据库这种有大量随机读、频繁触碰大量不同数据的应用页面访问模式是看起来很多东西都在用但实际工作集里高频命中的也就那么几个G。如果内核按LRU顺序一路回收下去很容易把那些偶尔会读回来但其实可以接受重新读一遍的数据和每次读回来都产生大量后续访问的数据混在一起处理。这时候内核对单个页面的下一次访问时间根本无法预测。它只能猜测这个页面最近才被访问过那以后也可能被访问。但从统计数据上看单页的访问历史预测能力非常有限真正有区分度的是一批页面构成的访问模式。于是就有了working set这个概念也就是进程在一定时间窗口内实际会访问的页面集合。如果内核估算出的working set大小超过了可用内存那无论如何都会抖动因为内存装不下需求但如果working set其实比可用内存小内核本可以更聪明地保留这些页面只是当前策略没做到。Refault Distance就是用来做这个更聪明的决策的。2.2 refault、distance和reclaim的三个关键定义先把三个容易混淆又互相纠缠的概念拆开。refault指的是一个页面被回收之后又被某个进程访问从而触发缺页重新读回。这个事件本身在每个内存紧张的系统里都在高频发生本身不是问题。问题是refault的频率和refault页面后续被继续访问的可能性。distance从字面上理解是距离。在原始论文和内核实现里它指的是一个页面从被回收到再次因为缺页被读回之间系统一共回收了多少个页面。为什么用回收页数而不是时间来度量因为时间是相对的在不同CPU频率、不同IO延迟的机器上没有可比性而回收页数在系统内部是有统一尺度的它直接对应着内存的周转率。一个页面在100毫秒内被refault和另一个页面在10万个页面被回收之后被refault后者说明系统内存周转剧烈。reclaim除了表示回收动作本身在Refault Distance的语境里更强调回收距离。内核的working set算法里有一个基本假设如果系统回收的页面总数小于工作集的大小那么被回收的页面会再次被访问。反过来如果回收的页面数已经超过工作集大小说明工作集已经缩小了或者回收的页面本身就不属于真正活跃访问的部分那后续再访问它的概率就很低。2.3 working set大小如何识别Refault Distance是尺子working set的识别如果从虚拟机监控的角度来看可以通过采样页表项来实现。但Linux内核没法为每个进程、每个内存区域做全量页表采样那样CPU开销不可接受。Refault Distance提供了一种轻量化的替代方案利用页面换出后重新进入的往返过程来自动标记哪些页面属于当前工作集。这里有个非常妙的逻辑。当一个页面被回收后如果它距离上一次访问已经经历了很多轮的回收活动那么当它再次被访问时它大概率已经不属于当前工作集内核不应将其激活回active链表而应任其待在inactive链表的尾部等待后续被回收。反之如果它被回收后很快就因为再次访问而refault说明这个页面仍然处在工作集内部那就应该被激活并且激活行为本身可以用来更新系统对工作集大小的估算。换句话说Refault Distance是一把尺子。尺子的刻度不是年月日时分秒而是系统回收页面的数量。通过这把尺子内核避免了每次refault都盲目激活页面的问题把这个页面值不值得留在内存里的判断从单页信息拓展到了整个内存回收过程的信息。3. 算法核心拆解shadow node、访问距离与激活判定3.1 shadow entry与shadow node的数据结构设计Refault Distance的实现依赖一套隐藏在radix tree / xarray里的数据结构叫shadow entry和shadow node。这个机制的背景是页面被回收时page cache的radix tree项会被移除但内核并不是完全丢掉这个页面的线索而是保留上一个被换出页面的访问时间戳和回收信息存在一个轻量的节点里也就是shadow entry。shadow node是这些entry的容器。在Linux 4.x以上的内核里xarray取代了radix treeshadow entry的实现也做了相应调整但核心思路是一样的。每个shadow node保存着一组指针指向被回收页面的信息包括回收时的时间戳准确说是jiffies时间戳。当一个缺页发生时如果对应的radix tree/xarray项已经不在但shadow entry还在内核就能知道啊这个位置之前有个页面走了现在又被访问了。shadow node本身占用的内存是受控的。Linux使用workingset_node_shadows、workingset_node_pages等计数来跟踪节点状态当node内部的shadow entry数降到零时node本身也会被回收。这避免了因为缓存回收元数据而导致内存膨胀的尴尬。不过shadow entry只是拿到了发生过refault的信号。真正要做激活判定还差一个关键信息就是这个页面被回收前在内存里已经存活了多久。这就是接下来要说的激活距离。本质上shadow entry里的时间戳和当前缺页时间之间的差值就是这个页面在内存中被换出后到再次被换入时经历的时间配合回收活动计数就可以计算出refault distance。3.2 activation distance的计算方式与关键参数从实现层面看页面被回收前会记录一个访问时间这个时间被存在struct page的某些字段在page cache场景下可以复用page-private等字段中。当页面被重新读入并触发refault时内核拿当前时间减去旧时间再把这个差值跟一个系统级阈值对比。这个阈值就是跟工作集大小直接相关的值。论文和内核里常用的简化公式是这样的activation_distance ≈ (refault_time - last_accessed_time) / sampling_period但实际在Linux workloads环境下计算方式更朴素。被回收时页面的激活信息被记录下来refault时内核从shadow entry读回这个信息。然后计算refault_distance refault_distance_from_shadow(entry)再把这个距离与active list长度和非活跃链表长度之间的关系做比较判断是否执行activate。这里有一个非常重要的内在关系refault distance和系统当前工作集大小之间的差值决定了激活行为。如果refault距离比较小说明这个页面离开后系统没经历太多reclaim工作集还在如果refault距离比较长说明已经发生了很多回收活动页面大概率已经不在工作集内。内核只需要维护一个全局的当前回收页面总数计数器再在每个shadow entry里存回收时的计数快照就能算出refault distance。参数上内核里定义了WORKINGSET_ACTIVATE_THRESHOLD这样的概念老版本内核里甚至有一个叫pagecache_limit的路径但现代内核的核心逻辑已经收敛到了workingset.c中的workingset_refault()函数。它接收一个shadow entry根据当前node状态返回一个布尔值决定是否对被refault的页面执行activate。3.3 workingset_refault的判定流程workingset_refault的函数实现里有几个核心步骤。首先从shadow entry中解析出旧的时间戳信息。内核用pack_shadow/unpack_shadow这两个宏来打包解包shadow entry因为一个xarray项里不止有时间戳还包括zone id、generation id等信息这些字段打包在一个unsigned long里。然后计算refault distance的真实值。距离的基本单位是内存回收页面的数量为了避免频繁访问node中所有shadow entryworkingset.c使用了一种压缩采样的思路把每个节点的refault信息和全局回收活动做换算。关键的判定逻辑大致是if (refault_distance active_list_size inactive_list_size) 则激活。为什么用active_list_size inactive_list_size因为这是当前系统里所有LRU页面的总数说白了就是内存里能装下的页面总量。如果一个页面在被回收后、再次被访问前系统回收了比所有LRU页面数量还多的页面说明它被换出之后内存已经完完整整地周转了一圈工作集已经换血这个页面不该被当作热数据。反过来如果refault distance小于这个值说明页面在被换出后系统还没来得及完成一轮完整LRU周转那么它很可能是无辜被换出的热页面应该被激活。这个判断在page cache场景下非常有效但在anon内存场景下有特殊情况需要处理后面我会专门讲。3.4 为什么LRU链表长度成为比较基准很多人会问为什么偏偏拿LRU链表长度当阈值而不是拿一个固定的时间值比如5秒、10秒原因是固定的时间值对负载变化不敏感。比如系统在疯狂读文件每分钟回收100万个页面和系统基本空闲每分钟回收100个页面你对一个页面做5秒的容忍度前者会导致大量refault被误判为热页面不断激活反而放大内存压力后者会导致真正热的数据在5秒后被误杀。而LRU链表长度是动态的。它直接反映着系统当前以多大速度在周转内存。当系统内存压力大LRU链表变短阈值变小refault被激活的门槛变高更多的页面会被拒之门外这反过来抑制了内存压力的进一步恶化。当系统内存充裕LRU链表变长阈值变大稍微有一点refault的页面就会被激活避免频繁读盘。这是一种自适应的反馈调节也是Refault Distance这套机制最有价值的工程点。它不是静态规则而是基于系统实时状态的动态决策。这也是它能在生产环境里真正起效的原因。4. 实操在内核源码里追踪Refault Distance的完整脉络4.1 内核版本与关键代码位置如果你现在的内核版本是Linux 4.20以后建议直接看mm/workingset.c和mm/vmscan.c。Refault Distance的核心实现在workingset.c里大概400行左右函数列表非常清晰。我以Linux 5.15 LTS内核为例来梳理这个版本是当前生产环境用得最多的长期稳定版之一代码逻辑也最典型。关键位置一include/linux/mmzone.h这里定义了workingset相关的zone状态、LRU链表状态。要注意page-pgmap和page-shadow这些字段在不同场景下的复用注释里有明确说明。关键位置二mm/workingset.c的workingset_refault()函数这是整个算法的决策核心。关键位置三mm/vmscan.c的shrink_page_list()页面被回收时如果发现是page cache页面就调用workingset_eviction()来记住回收信息。4.2 页面回收路径workingset_eviction在vmscan.c的shrink_page_list()里当一个page cache页面被回收时会走到一个分支if (PageWorkingset(page)) { workingset_eviction(page, target_memcg); }这里的PageWorkingset标志表示这个页面曾经被识别为工作集的一部分。workingset_eviction做的事情就是把这个页面的回收信息打包进shadow entry。具体包含当前的workingset代际、回收时的时间戳、zone信息。打包后存入page cache对应的xarray项中后续如果页面被重新读回xarray项查不到真实页面但能查到shadow entry就能触发refault路径。有个细节值得注意shadow entry的存续时间长度受内存回收活动影响。如果系统一直在回收内存被替代的shadow entry也会被清理。这保证了shadow节点不会无限累积。4.3 缺页路径workingset_refault的完整执行页面被重新读入时缺页处理函数会调用find_get_entry()此时发现xarray项是shadow entry就会调用workingset_refault()。我来贴一段关键逻辑的伪代码帮助理解static bool workingset_refault(void *shadow) { unsigned long refault_distance; struct pglist_data *pgdat; unsigned long active_file; unsigned long inactive_file; unsigned long active_anon; unsigned long inactive_anon; /* 从shadow entry中解包出本次refault的时间戳信息 */ refault_time unpack_shadow(shadow, zone_id, memcgid, workingset_id); /* 计算refault distance——从shadow entry记录时刻到现在的内存回收量 */ refault_distance atomic_long_read(node-inactive_age) - refault_time; /* 获取当前内存节点的各类链表长度 */ active_file lruvec_page_state(lruvec, NR_ACTIVE_FILE); inactive_file lruvec_page_state(lruvec, NR_INACTIVE_FILE); active_anon lruvec_page_state(lruvec, NR_ACTIVE_ANON); inactive_anon lruvec_page_state(lruvec, NR_INACTIVE_ANON); /* * refault distance小于所有可回收页面总数时认为该页面仍然属于工作集 * 这个所有可回收页面就是active_file inactive_file active_anon inactive_anon */ if (refault_distance active_file inactive_file active_anon inactive_anon) return true; return false; }这个函数返回true缺页路径就会对刚读回的新页面设置PageActive标志并把它放到active链表返回false新页面就放在inactive链表尾部随时可以被回收。注意这里有一个非常关键的细节refault_distance使用了一个叫inactive_age的计数器。这个计数器会随着每次内存回收而递增。把时间转化为年龄的思路是整个算法里最精妙的地方。它不关心页面在物理时间上离开了多久只关心在回收活动中经历了多少岁月。这天然地适应了不同负载特征的系统且不依赖于一个全系统统一的时钟精度。4.4 使用tracepoint观测refault行为实际调试时直接读源码容易一头雾水最好的办法是用内核自带的tracepoint。Linux 4.19之后mm/workingset.c里增加了几个tracepoint配合ftrace可以直接观测refault的实时行为。常用的tracepoint有workingset_refault页面发生refaultworkingset_activate页面被判定为需要激活workingset_restore页面在anon内存场景被恢复追踪命令cd /sys/kernel/debug/tracing echo workingset_refault set_event echo 1 tracing_on cat trace_pipe这样能看到每个refault事件的时间戳、进程名、页面地址。再配合perf的pagefault事件统计就能定量分析系统的refault频率验证算法是否在正常工作。我个人习惯还会配上一份cgroup的内存压力监控cat /sys/fs/cgroup/memory/memory.pressure或者新版cgroup v2下的cat /sys/fs/cgroup/memory.pressure4.5 用bpftrace观测内核函数的判定结果如果tracepoint不够满足你可以用bpftrace挂到workingset_refault函数上直接看内核判定结果分布。kprobe:workingset_refault { refault_total count(); } kretprobe:workingset_refault { if (retval 1) { activate_total count(); } }跑一段时间后如果activate_total和refault_total的比例明显偏高比如激活率达到80%以上说明系统里大量refault页面都被判定为热数据如果比例极低说明工作集已经超出内存能力系统正在持续抖动。这两种状态需要不同的优化策略。通过这样的观测你能直观地看到算法运行的最终效果而不是停留在源码分析层面。这套观测方法对排查生产环境的内存问题非常实用。5. 实操模拟refault场景与验证算法效果5.1 构造一个可重复的实验环境要真正理解Refault Distance光看代码和tracepoint还不够最好自己动手构造一个refault场景观察算法在不同参数下的行为。我推荐用两种方式构造方式一受限内存容器跑文件读循环。用docker或cgroup v2限制一个容器内存为256MB然后在里面执行一个循环读文件的程序读取的数据集大小为1GB。此时page cache容量远小于数据集必然出现大量refault。在这个场景下可以观察Refault Distance算法对页面激活的抑制效果。方式二用fio加内存压力。先用一个大文件读取把page cache填满再用另一个进程不断写匿名内存制造内存压力触发page cache的回收。然后继续读文件观察refault行为。实验环境建议使用一台独立虚拟机或容器内核版本5.15以上内存不少于4GB避免宿主机的其他负载干扰观测结果。5.2 实验步骤与内核参数我以方式一为例给出完整步骤。# 创建一个内存受限的cgroup mkdir /sys/fs/cgroup/memory/refault_test echo 268435456 /sys/fs/cgroup/memory/refault_test/memory.limit_in_bytes # 把当前shell放入cgroup echo $$ /sys/fs/cgroup/memory/refault_test/tasks # 生成1GB的测试文件 dd if/dev/urandom of/tmp/testfile bs1M count1024 # 循环读文件 for i in $(seq 1 20); do dd if/tmp/testfile of/dev/null bs1M count1024 2/dev/null done执行完循环后回头查看cgroup的内存统计cat /sys/fs/cgroup/memory/refault_test/memory.stat重点关注pgpgin、pgpgout、pgmajfault这几个字段。pgmajfault如果居高不下说明系统在频繁缺页并且这些缺页不是completed从page cache里直接命中的。同时用前面提到的tracepoint观察cd /sys/kernel/debug/tracing echo workingset_refault set_event echo workingset_activate set_event echo 1 tracing_on sleep 10 cat trace | head -100注意观察activate事件的比例。如果refault很多但activate很少说明算法在正确压制非工作集页面的激活。5.3 不同内存限制下的行为对比为了看出Refault Distance的差异化决策建议对比三组实验第一组内存限制1GB数据集1GB这种情况内存刚好放得下理论上有足够的缓存空间不应该有大量refault。第二组内存限制512MB数据集1GB内存大约只有数据集一半refault必然大量发生此时激活率会呈现一个中间状态。第三组内存限制128MB数据集1GB内存严重不足refault量巨大但激活率应该极低因为算法会判断这些页面即使激活也存不下来不如不激活。我把在不同限制下观察到的典型数据列成一个表内存限制数据集大小refault量activate比例主要现象1GB1GB较少中度偏高缓存基本命中refault多因首次读取512MB1GB中等偏多中度缓存部分命中抖动适中128MB1GB极多偏低持续颠簸激活无法解决问题这个表的价值在于它直观地展示了Refault Distance的自我调节特性。内存越紧张激活越多但激活率反而降低甚至低于内存充足时。因为内存压力大LRU链表总长度缩短refault_distance超过阈值的可能性反而变大。算法不会因为内存紧张就硬把所有refault都当作热数据来处理它会精确地感知到此页面即使激活很快也会被再次回收从而选择不激活。5.4 一个典型的生产场景文件服务器内存抖动我在实际工作中遇到过类似案例。某对象存储网关节点内存64GB后台会持续扫描大量小文件同时有用户请求读取这些文件。系统出现周期性抖动表现为客户端时延飙高。排查时发现pgmajfault周期性暴增而pgsteal也同步增长。用perf top观察发现shrink_page_list占CPU比较高同时workingset_refault频繁被调用。进一步用tracepoint追踪后发现大量refault被判定为false即不激活因此页面读回后依然在inactive链表尾部很快又被回收再访问时再次refault。这形成了一个死循环。后来分析发现问题不在Refault Distance算法本身而在于系统里有一个进程在做扫描工作把page cache全部污染了。真正频繁访问的热数据占比其实不高但丧尸进程产生的扫描页面把LRU链表搅得天翻地覆。最终修改方案是给扫描进程设置独立的cgroup限制其page cache占用并对用户请求线程的cgroup设置更高的内存优先级。这样改动后refault不再大量发生时延恢复正常。这个案例给一个非常实用的启示Refault Distance不是万能药。它在合理的内存分区下能高效工作但如果系统里有不当的大规模线性扫描算法本身无法区分只能把压力全部体现在refault和页面激活的频繁切换上。这类问题需要结合cgroup、ionice等手段从源头治理。6. 常见问题与排查技巧实录6.1 refault距离太短导致的缓存命中率下降症状page cache命中率从95%以上掉到80%以下应用时延明显上升内存并没有被完全占满。排查思路先确认是否存在refault事件的突增。用perf trace或bpftrace统计workingset_refault的调用次数如果数量很高但activate比例不高说明大量refault被判定为不需要激活。可能原因之一是inactive链表长度被压缩得很短。Refault Distance算法的阈值是active_list inactive_list如果其中一个几乎为空阈值就会变小。比如文件系统缓存都跑到了active链表inactive链表空荡荡那么inactive_age的推进速度会变得异常快导致refault距离计算偏大正常热页也被误伤。解决办法关注内核文档提到的sc-file_is_tiny逻辑。当文件缓存总量太小通常小于总内存的某个比例比如2%时内核会跳过workingset_refault的激活判定直接激活所有refault页面。这是为了避免文件缓存过小时所有refault都被压制的极端情况。如果你的系统里文件缓存经常处于低水位靠这套算法的自动调节是救不回来的必须从业务层面减少内存占用或者用其他手段限制anon内存的挤占。6.2 anon内存场景下refault distance失真匿名页面的回收和page cache回收有一个本质区别匿名页面回收时必须先写入swap读回时也走swap。这意味着一次anon refault的代价比page cache refault高一个数量级。但在workingset_refault的实现里anon页面的distance计算和file页面用的是同一套算法没有区分代价差异。解决这个问题的倾向是给anon设置更高的激活优先级。我在生产里也做过类似的调整通过调整vm.swappiness参数来间接影响anon页面的回收倾向。如果设备有足够的swap空间还可以把swap换入换出的代价降下来。但如果swap本身在慢速磁盘上那可能要从业务层做处理减少匿名内存压力的冲击。6.3 refault distance出现负数理论上refault_distance inactive_age - refault_time如果refault_time快照晚于inactive_age会得到负数。这种情况说明shadow entry的时间戳比当前inactive_age还新通常由两类原因引起第一类shadow entry长时间不被访问后workingset_node里存储的refault信息已经过期但node还没有被回收。内核维护了一个refault_inactive的定时清理逻辑在workingset_refault里如果检测到distance异常比如UVISITED会视为无效shadow entry直接返回false不激活。第二类RPSRemote Process Service或cgroup迁移导致的内存节点切换shadow entry里的数据在迁移中出现了不匹配。这类问题内核在较新的版本中通过增加generation校验来缓解。实际排查时可以在bpftrace脚本里打印distance值如果发现大量负数先检查内核版本再看cgroup配置是否频繁变更。6.4 THP和大页对refault distance的影响透明大页开启后页表的基本单位变成2MB。当一个2MB大页被回收再次触发refault时内核要按base page4KB来处理此时一个THP会被拆分成512个base page每个base page都要经过一次refault判定。这个场景下Refault Distance的计算结果会被放大因为inactive_age的单位是base page。举个例子一个THP被回收内核记录了一个时间戳。当它被重新读回触发refault时distance计算出来可能是几万个页面但实际上只是一个2MB大页的回收。这种情况下如果阈值不够高可能直接判定为不需要激活导致大页读回后又被晾在inactive链表尾部很快再次被回收。生产环境如果频繁使用THP建议关注thp_nr_page_cache_swpout等统计指标必要时对特殊情况设置thp的split行为。不过常规场景下内核已经通过unmapping和split逻辑做了很多保护不会让这个场景无限恶化。6.5 用memory.reclaim和memory.peak辅助判断在cgroup v2下可以直接用memory.reclaim接口来主动触发某个cgroup的内存回收配合内存压力事件可以快速复现refault相关的问题。做法是监控memory.pressure里的si_mem_available值当它掉到很低时手动执行echo 1M /sys/fs/cgroup/xxx/memory.reclaim然后观察memory.stat里的pgscan和pgsteal以及psi文件里的cpu时间。如果psi的some/avg值在高位持续十有八九系统在经历严重的refault循环。用这个方法来区分内存真的不够和内存有但分配不均两种场景能少走很多弯路。7. 多一点实用经验我在实际调优中最大的体会是Refault Distance不是一个可以拿着文档照搬参数的算法它更像一个动态标尺需要结合业务访问模式来理解。它对连续顺序读的场景优化效果有限对随机读和小文件密集访问场景的优化效果最明显。如果你的应用表现出refault高、activating高、但时延没有恢复那通常不是算法设置问题而是业务模式本身需要调整比如减少非热点数据的内存缓冲或者在应用层做数据热度的预分类。另外调试这类问题时一定要记住页面回收量和时间并不是一回事。用inactive_age而不是时间戳作为尺子是这套算法的灵魂。理解了这一点你就不会再犯为什么我的系统空闲时refault反而更多这类方向性错误了。还有一个容易忽略的小技巧在cgroup v2环境下不同内存cgroup的refault距离是独立计算的。如果你有多个业务容器共用同一台机器最好为每个容器单独监控workingset_refault的tracepoint避免把容器间的内存回收活动混在一起分析。实际效果上容器级别的隔离越严格refault distance的判定越准确。Refault Distance在btrfs等文件系统的缓存管理上已经有实际落地未来如果内存资源更加紧张、混部场景更多这套机制会是内存QoS里不可绕过的基础设施。现在花点时间把它吃透以后碰到内存抖动、page cache命中率掉坑排查起来会顺手很多。
返回列表