← 中文资源

简体中文版本

Redis — fork 与换页造成的周期性延迟尖峰

用 LATENCY HISTORY 追查后台保存 fork 与 OS 换页造成的 Redis 延迟尖峰,测量 fork 速率,并修复内存过量提交与 THP。

阅读英文原文

症状

  延迟监视器能把真实原因从传闻里分离出来:latency history fork 显示 412 毫秒的 fork 停顿,latest_fork_usec 同量级,rdb_last_save_time 与尖峰时间戳对齐,vmstat 同窗口出现 si/so 换页活动。机制分三段:fork 为整个 RSS 复制页表;随后每次写入触发按页粒度的写时复制缺页;换页让任何停顿雪上加霜——客户端等待时页面还要从磁盘拉回来。–latency 的周期性锯齿与保存间隔同步,是完整的外部指纹。

诊断思路

  先用 latency history 区分事件类别(fork、aof-fsync、swap 等)并与保存时间对齐;再用 vmstat 与 smaps_rollup 检查换页活动与 AnonHugePages——非零 si/so 即在换页,THP 命中说明写时复制粒度被放大到 2MB;最后核对 overcommit_memory 与 THP 配置,overcommit=0 会让分配器在压力下拒绝 fork。

根因

  持久化 fork 要复制整个数据集的页表。RSS 很大时 fork 本身耗时可见,之后的每次写入都触发成本随页粒度放大的写时复制缺页;叠加宿主机换页压力,主线程在保存与重写事件对齐的周期性尖峰里停摆。

修复与验证

  先改宿主机物理层:vm.overcommit_memory=1 让 fork 成功;THP 设 never 恢复 4KB 的 COW 粒度。再调实例规模与节奏:maxmemory 留出 fork 余量(16GB 主机上 6GB 而不是 14GB);保存策略放宽(save 900 1 而不是高频保存);写密集数据集用 everysec 的 AOF 常常优于频繁 RDB 快照,还可以让持久化关闭的副本专供读。验证时调优后 latest_fork_usec 每 GB 保持数百微秒内、latency history 无 fork 事件、–latency 在保存周期全程平坦。直接关掉持久化等于拆掉安全网来消尖峰——除非数据真的可有可无,否则调优而不是删除。

代码示例

redis-cli latency history fork
1) 1) "1729584000"
   2) (integer) 412         <- 412 ms fork stall
sysctl vm.overcommit_memory=1       # let the fork succeed
echo never > /sys/kernel/mm/transparent_hugepage/enabled   # 4KB COW granularity
# redis.conf — shrink memory so fork has headroom:
maxmemory 6gb            # on a host with 16gb, leaving room to COW

# and spread the save pressure:
save 900 1               # instead of frequent saves under write load