MGLRU 回收循环
老化 / 扫描量 / 回收
三件事捏在一起
dirty flush 在循环外
耦合点
每轮重算 nr_to_scan
扫描量随老化与轮转变化
DEF_PRIORITY 可跳过老化
非默认优先级才推进
脏页路径
dirty folio 特殊处理
flush 等整个循环结束
flusher 响应偏弱
生产环境偶发 OOM
本系列
起始算清扫描预算
老化 / 脏页移入循环
旧循环 · 每轮重算
while (true) {
nr_to_scan = get_nr_to_scan()
evict_folios(nr_to_scan)
扫描量怎么来
按 LRU 长度求和
仅在不需要老化
或 DEF_PRIORITY 时移位
计算与老化 / 轮转耦合
老化被拖住
需老化且非 DEF 才 try_to_inc_max_seq
返回 -1 立刻停止本轮
并发回收可能一起失败
DEF_PRIORITY 仍可能空转
后果
白费一轮迭代
过度回收 slab / 破坏平衡
新循环 · 一次预算
nr_to_scan = get_nr_to_scan()
while (nr_to_scan > 0) {
nr_to_scan -= delta
预算在入口定下
evictable = lruvec_evictable_size()
apply_proportional_protection()
nr_to_scan >>= sc->priority
始终尊重回收优先级
循环内只消耗
min(nr_to_scan, MIN_LRU_BATCH)
delta = evict_folios(nr_batch)
nr_to_scan -= delta
不再每轮重算
边界
预算在循环入口固定
极小 lruvec 可能跳过本轮
老化 · 不再空转
should_run_aging()
try_to_inc_max_seq()
should_age = true
判定顺序
eviction 已不可能
先返回 true
否则 DEF_PRIORITY 才跳过
只在仍可 eviction 时跳过
继续扫描 / 公平
cgroup 不因老化停
公平交给上层迭代器
root 回收一批后停
rotation 仍保留
效果
老化需要时立即推进
少空转 / 少意外 OOM
脏页 · 循环之外
sort_folio() 特殊处理
dirty file 放到次老代
flush 等整个循环结束
特殊 reactivation
跳过 isolation
LRU 尾部更常见
回收效率下降
检查精度不足
旧触发条件
sc->nr.unqueued_dirty != 0
&& sc->nr.unqueued_dirty == sc->nr.file_taken
循环结束后才检查
flusher 响应偏弱
后果
脏页被推到 LRU 尾部
writeback 挡住回收
脏页 · 移入循环
shrink_folio_list()
dirty / writeback
flush 紧随其后
统一 reactivation
dirty / writeback 取出后判断
走 common routine
writeback 完可再轮转
不再放次老代
循环内唤醒
stat.nr_unqueued_dirty == isolated
wakeup_flusher_threads()
!writeback_throttling_sane()
reclaim_throttle()
关键
flush 不再等循环结束
脏页挡住回收就唤醒
测试环境
48c96t / NUMA 2 节点
128G 内存 / NVME
10G cgroup / WiredTiger cache 4.5G
负载
MongoDB YCSB workloadb
recordcount 20M
operationcount 6M / 32 线程
95% read / 5% update
结果
吞吐 60,653.5 → 82,384.35
+35.8%
文件 refault -44.7%
pgpgin -31.5%
口径
MongoDB 单 workload
全程无 swap 介入
边界
仅 48c96t / 128G / NVME
10G cgroup / 4.5G cache
无 swap,隔离文件 LRU
慢 IO
HDD / 网络存储
作者报告增益更大
部分 workload >100%
不是所有场景
其他
常见 benchmark 无回归
LOC 减少
意外 OOM 减少
均为作者报告
不要外推
吞吐数字只属该配置
慢 IO 结论也来自作者
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x