bigywater.tech
Redis · 66

持久化对延迟的影响

RDB 快照和 AOF 重写都要 fork 一个子进程,这一下 fork 是主线程上唯一「随数据量增长」的停顿。多长?会不会拖累所有请求?这篇用三个大小的数据集实测 fork 耗时,并且在持续写入时看它到底体现成多大的延迟。

1.3 → 21.1 ms
fork 耗时:17 MB → 498 MB(约 41 µs/MB)
21.4 ms
498 MB 数据集 BGSAVE 期间 SET 的最大延迟(≈ fork 耗时)
29 µs
BGSAVE 期间 SET 的 p50,与没有 BGSAVE 时相同

做法与结果

实测 · 本机执行输出
    key 数    内存 MB   fork µs  BGSAVE 耗时 |   基线 p50    基线 max |   期间 p50   期间 p99    期间 max |  COW MB
   100000       17      1336      0.11s |       26       406 |       28       59      1628 |     0.0
  1000000      159      6477      0.60s |       27      1504 |       29       51      6817 |     0.0
  3000000      498     21090      1.83s |       27      1103 |       29       67     21413 |     0.0

说明:fork µs 来自 INFO stats 的 latest_fork_usec;COW MB 来自 INFO persistence 的 rdb_last_cow_size;延迟单位 µs。

每个数据集一个新实例(save "",没有自动快照)。装入 key 后,单客户端不停随机覆盖已有 key;先测 2 秒基线,再发 BGSAVE,统计从发出到 rdb_bgsave_in_progress 回到 0 期间每条 SET 的延迟。基线 p50 只有 26–27 µs(与数据集大小无关)。

fork 耗时随内存线性增长

latest_fork_usec:17 MB 是 1.3 ms,159 MB 是 6.5 ms,498 MB 是 21.1 ms;后两者每 MB 约 41–42 µs。fork 需要复制父进程的页表,内存越大,页表越大——这是我对线性增长的解释,没有单独验证页表大小。据此,在这台机器上外推:1 GB 约 40 ms,10 GB 约 400 ms(只是按线性外推,没有实测;不同操作系统、是否使用大页,差别很大)。

停顿体现在延迟上

BGSAVE 期间,SET 的最大延迟是 1.6 / 6.8 / 21.4 ms,几乎等于各自的 fork 耗时;而 p50 仍是 29 µs 左右、p99 也只有 59–67 µs。也就是说停顿只发生在 fork 的那一下,子进程之后写快照,不占用主线程。对「p99 之内」的指标它没有影响,对「最大延迟」有。

COW:这台机器没量到

子进程运行期间,主进程修改的页会被复制(写时复制),额外占内存。我让客户端在 BGSAVE 期间随机覆盖写,但 rdb_last_cow_size 在这台 macOS 上始终是 0,日志里的 Fork CoW … 0 MB 也是 0——这个指标在这里不可用,所以我没有量到 COW 的内存开销。想量它需要在 Linux 上做。

分步演示:fork 的代价

持久化对延迟的影响
本步的数据

    本文用到的工具与边界

    • 本机 macOS 27 / Redis 8.10.2 独立实例(本机回环端口,save 关闭),redis-cli 风格输出由自写的 RESP 客户端打印;计时用 time.perf_counter,数字每次运行会有小幅抖动。BGSAVE 用 save "" 关闭自动快照;每个数据集只测一次;fork 的成本取决于操作系统与硬件,Linux 上(特别是有 THP 时)差别会很大
    • 没有验证:Linux 上的 fork 耗时与 COW 内存、大页(THP)的影响、AOF 重写的 fork(应与 BGSAVE 类似但没有单独测)、多个并发客户端下的延迟、超过 500 MB 的数据集(1 GB、10 GB 的数字是线性外推)
    京ICP备2026067502号-1京公网安备11010502063487号