背景
vm.defrag_mode=1 下,non-movable 分配只从 non-movable pageblock 取页,不侵占 movable 块。当 non-movable pageblock 耗尽时,请求进入 allocator slowpath,reclaim 和 compaction 被提升到 pageblock_order 运行,以生产整块 non-movable pageblock。
但 slowpath 的重试决策仍按请求 order 判定。order-0 请求的 reclaim 在 pageblock_order 运行时,一旦发现 zone 看起来 compaction_ready() 就提前返回,报告了进展(返回 1)却什么都没回收,还跳过了本应对 memory.low 保护的 cgroup 做回收的重试。compaction 在 pageblock_order 失败或被 defer。然后 should_reclaim_retry() 按 order-0 判定,把这个空进展当作真实进展,重置 no_progress_loops,请求继续重试。compaction 重试检查也按请求 order 判定,与 promoted order 的实际结果脱节。
promoted order 与重试决策脱节导致死循环
┌───────────────────────────────────────────┐
│ order-0 non-movable 请求进入 slowpath │
└─────────────────────┬─────────────────────┘
▼
┌───────────────────────────────────────────┐
│ reclaim/compaction 提升到 pageblock_order │
│ 运行 │
└─────────────────────┬─────────────────────┘
▼
┌───────────────────────────────────────────┐
│ reclaim 发现 compaction_ready 提前返 │
│ 回,报告进展但没回收 │
└─────────────────────┬─────────────────────┘
▼
┌───────────────────────────────────────────┐
│ should_reclaim_retry 按 order-0 判定, │
│ 进展重置 no_progress_loops │
└─────────────────────┬─────────────────────┘
▼
┌───────────────────────────────────────────┐
│ 无限重试,永远到不了 ALLOC_NOFRAGMENT │
│ fallback │
└───────────────────────────────────────────┘
Meta 生产机上(64G、defrag_mode、memory.low 覆盖大部分负载),95% 的 direct reclaim 是无产出的 order-9 运行,峰值 60k 次/秒。全天约 2 亿次 should_reclaim_retry() 调用中 no_progress_loops 始终为 0。空转的是 SLUB inode/dentry refill,表现为内存压力并触发 PSI OOM 杀掉业务与系统服务。
问题
- reclaim 提升到 pageblock_order 后空进展被当作 order-0 的真实进展
- no_progress_loops 被反复重置,order-0 请求无限重试
- compaction 重试检查按请求 order 判定,与 promoted order 结果脱节
- 请求永远到不了 ALLOC_NOFRAGMENT fallback,形成死循环
方案
让重试决策跟随 promoted order。新增 nofrag_promote_order() 在 slowpath 每次迭代统一计算 promoted order,传给 direct reclaim、direct compaction 和两个重试 helper。
重试决策跟随 promoted order
┌────────────────────────────────────────┐
│ nofrag_promote_order 统一计算 promoted │
│ order │
└───────────────────┬────────────────────┘
▼
┌────────────────────────────────────────┐
│ reclaim 进展不再重置 no_progress_loops │
└───────────────────┬────────────────────┘
▼
┌────────────────────────────────────────┐
│ compaction 按 promoted order 检查结 │
│ 果 │
└───────────────────┬────────────────────┘
▼
┌────────────────────────────────────────┐
│ COMPACT_SKIPPED 不再重试,可 fallback │
└───────────────────┬────────────────────┘
▼
┌────────────────────────────────────────┐
│ fallback 时重置重试计数器,获得完整 │
│ 预算 │
└────────────────────────────────────────┘
reclaim 方面:promoted 请求的进展不再重置 no_progress_loops,与 costly order 的处理方式一致。这样空进展会累积计数,最终触发 fallback 而不是无限重试。
compaction 方面:should_compact_retry() 按 promoted order 检查 compaction 结果。COMPACT_SKIPPED 时 promoted 请求不再重试,因为可以走 fallback。但 compaction 优先级下限和 COMPACT_SUCCESS 的重试上限仍按请求 order 判定,保证非 costly 请求在 fallback 之前仍有机会走到 COMPACT_PRIO_SYNC_FULL。
fallback 方面:走 ALLOC_NOFRAGMENT fallback 时重置 no_progress_loops 和 compaction_retries,让 fallback 尝试获得完整的重试预算。
收益
32G VM,defrag_mode,memory.low 下 inode churn 复现器:
| 指标 | Before | After | 变化 |
|---|---|---|---|
| should_reclaim_retry() 调用次数 | 63M | 293k | -99.5% |
| 峰值内存压力(PSI some avg10) | 99% | 12% | -87pp |
| 文件创建速度 | — | — | 提升 5.7 倍 |