fork 与写时复制
快照与 AOF 重写都依赖同一个操作系统能力:fork。调用瞬间内核为子进程复制一份父进程的页表,父子进程随后共享同一批物理内存页,这一过程本身非常快。等到任意一方要修改某个页面时,内核先把该页复制出一份新副本给对方,父子各自写自己的副本,这就是写时复制(COW)。子进程因此能在「时间静止」的视图上遍历数据,父进程可以继续接收写入。
基本信息
| 复制粒度 | 内存页,通常 4KB,与数据内容无关 |
|---|---|
| fork 开销 | 页表拷贝,与实例内存量近似线性 |
| 观测指标 | INFO stats 的 latest_fork_usec,单位为微秒 |
| 内存影响 | 快照期间写入越多,被复制的页越多,RSS 上升越明显 |
| 相关机制 | 透明大页(THP)会放大 fork 与 COW 的开销 |
典型使用场景
- 容量规划:按实例内存估算 fork 停顿与快照期间的额外内存占用,预留副本空间
- 排查周期性延迟抖动:请求延迟每隔一段时间出现尖峰,往往是快照或重写的 fork 造成
- 判断快照是否会触发 OOM:内存水位高时 fork 后的 COW 增长可能把实例推向淘汰或崩溃
- 优化大实例的运维窗口:把快照与批量导入安排在低峰,避免写入放大 COW 复制量
- 评估容器内存限制:容器内存上限必须覆盖数据加 COW 峰值,否则会被调度器直接杀掉
观测与配置
观测与配置
redis-cli INFO stats | grep latest_fork_usec # fork 耗时,微秒
redis-cli INFO memory | grep -E 'used_memory_human|used_memory_rss_human'
redis-cli INFO persistence | grep rdb_bgsave_in_progress
cat /sys/kernel/mm/transparent_hugepage/enabled # 建议为 madvise 或 never
sysctl vm.overcommit_memory # 大内存实例建议设为 1指标读法
| 指标 | 正常表现 | 异常信号 |
|---|---|---|
| latest_fork_usec | 与内存规模匹配,通常数毫秒到数十毫秒 | 达到数十万微秒说明停顿严重 |
| used_memory_rss | 略高于 used_memory | 快照期间大幅飙升,接近容器上限 |
| rdb_bgsave_in_progress | 短暂为 1 | 长期为 1 说明快照卡住 |
| THP 状态 | madvise 或 never | always 会显著放大 fork 与 COW 开销 |
为什么 fork 耗时与内存量相关:子进程需要一份完整的页表副本,内存越大页表层级与条目越多,拷贝越久。这也是大实例必须限制单实例内存规模的原因之一,把 100GB 拆成多个较小实例,单次 fork 的停顿会明显缩短。
COW 带来的内存增长无法精确预测,它取决于快照期间被修改的页面数量。在内存水位已经很高的实例上触发快照,可能瞬间超出物理内存导致 OOM 或被容器杀掉,快照窗口应避开批量写入并预留余量。