RDB 快照和 AOF 重写都要 fork 一个子进程,这一下 fork 是主线程上唯一「随数据量增长」的停顿。多长?会不会拖累所有请求?这篇用三个大小的数据集实测 fork 耗时,并且在持续写入时看它到底体现成多大的延迟。
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(与数据集大小无关)。
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 之内」的指标它没有影响,对「最大延迟」有。
子进程运行期间,主进程修改的页会被复制(写时复制),额外占内存。我让客户端在 BGSAVE 期间随机覆盖写,但 rdb_last_cow_size 在这台 macOS 上始终是 0,日志里的 Fork CoW … 0 MB 也是 0——这个指标在这里不可用,所以我没有量到 COW 的内存开销。想量它需要在 Linux 上做。