大量脏页堆积但 iostat %util 不高的 page cache 异步回写优化

3次阅读

脏页积压主因是vm.dirty_background_ratio过低导致回写过早且不畅,而非vm.dirty_ratio未达阈值;需同步调高background_ratio(15–25)、dirty_ratio(30–40),并缩短dirty_expire_centisecs(1000–1500)以均衡回写节奏。

大量脏页堆积但 iostat %util 不高的 page cache 异步回写优化

为什么 vm.dirty_ratio 调高了,脏页还是积压?

因为内核不会等脏页涨到 vm.dirty_ratio 才开始回写——它更早就会触发异步回写,而触发阈值由 vm.dirty_background_ratio 控制。如果这个值太低(比如默认 10),系统会频繁唤醒 pdflush(或现代内核的 writeback 线程),但线程本身可能被 I/O 延迟卡住、或受限于设备吞吐,导致脏页“产速>写速”,越积越多。

常见错误现象:iostat -x 显示 %util 持续低于 30%,但 /proc/meminfoDirty:Writeback: 居高不下,应用 write() 延迟升高。

  • vm.dirty_background_ratio 建议调至 15–25(视内存总量而定),避免过早、过碎的回写打断业务 IO
  • vm.dirty_ratio 可同步上调至 30–40,为突发写留出缓冲空间,但别超过 50,否则 sync() 或内存回收时容易卡死
  • 必须配对调整 vm.dirty_background_bytesvm.dirty_bytes(二者与 *_ratio 互斥),否则 ratio 设置会被忽略

vm.dirty_expire_centisecs 设太长,脏页就“赖着不走”

这个参数决定脏页在内存里最多“躺”多久才必须被回写(单位是厘秒,即 1/100 秒)。默认 3000(30 秒),看似宽松,但在高吞吐写场景下,大量脏页会在 30 秒内反复被标记为“可回写”,却因 writeback 线程调度或磁盘队列阻塞迟迟没发出,最终全部挤在 expire 临界点前集中冲刷,造成 I/O 尖峰。

使用场景:SSD 或 NVMe 后端、日志型写入(如 kafka broker、数据库 WAL)、容器环境共享宿主机 page cache。

  • vm.dirty_expire_centisecs 从 3000 降到 1000–1500(10–15 秒),让回写节奏更均匀
  • 注意:设太短(如
  • 该值不影响已进入 Writeback: 状态的页,只约束“脏了但还没排队”的页

为什么 iostat %util 看着不高,磁盘其实已经饱和?

%util 是基于设备忙闲时间统计的,对 NVMe 或多队列 SCSI 设备意义很弱——它只看单个请求队列是否 busy,而现代存储能并行处理数百请求。实际瓶颈常在文件系统层(如 ext4 journal 锁)、块层调度器(cfq 已弃用,但 mq-deadline 的 deep queue 行为难预测),或 RaiD 卡缓存策略上。

性能影响:%util 30% 时,await 可能已超 20ms,svctm 失真,avgqu-sz 持续大于 4 就说明队列深度压满。

  • 优先看 iostat -x 1avgqu-szawait,而非 %util
  • 确认存储:裸盘?LVM?mdadm?ZFS?不同层有各自的缓存和限流逻辑,page cache 回写会穿透所有层
  • perf record -e 'block:*' -a sleep 10 抓块层事件,看 block_bio_queue 是否

容器或 KVM 里改 vm.dirty_* 参数没效果?

因为 cgroup v1 的 memory 子系统默认不隔离 page cache 脏页控制参数;cgroup v2 虽支持 memory.pressure,但 vm.dirty_* 仍是全局 sysctl,容器内修改只作用于自身命名空间,宿主机内核仍按原值调度 writeback 线程。

兼容性影响:kubernetes Pod 的 securityContext.sysctls 只允许 fs.*net.* 等白名单,vm.* 默认禁止写入,强行加会启动失败。

  • 必须在宿主机层面统一调优,容器内仅可通过 sync()fsync() 主动干预,或挂载 noatime,nobarrier 减少元数据写压力
  • 若用 systemd-run 启动服务,可用 --scope --Property=MemoryLimit=... 配合 vm.swappiness=1 间接减少脏页生成
  • 云厂商自研存储(如阿里云 ESSD、AWS io2)通常关闭 host 端 writeback,依赖实例内应用直写,此时调 vm.dirty_* 完全无效

最易被忽略的一点:脏页堆积未必是回写慢,也可能是应用持续 write() + mmap(MAP_SHAred) 修改,且没调 msync(),导致 page cache 脏页生命周期完全脱离内核 writeback 控制节奏。

text=ZqhQzanResources