先定延迟预算,再选连接数
Lavik 9 月 18 日的 SPDK 连接数扫描说明,吞吐量最高的测量点未必适合作为运行点。加上延迟约束后,比较结果会发生变化。
为 Redis 兼容服务规划容量时,更有用的数字往往是满足延迟预算的吞吐量。Lavik 9 月 18 日的 SPDK 基准给出了一个具体例子:在一千万键 GET 测试中,越过吞吐量峰值后再将连接数翻倍,吞吐量反而下降,p99 则明显升高。对于考虑用 NVMe 承载值数据的团队,完整的连接数扫描比最高的一根柱子更有参考价值。
Lavik 是采用 Apache 2.0 许可证的 beta 项目,在 DRAM 中保留紧凑的键索引,将值放在存储设备上。本次基准使用下载的标准版 0.1.0-beta.1 软件包,源码修订为 3955b98d43b312324aa8d52775df52cfb111c0d0,存储采用 SPDK,网络采用内核 TCP。这些结果不代表 minimal 软件包的 io_uring 路径。
峰值之后的测量点同样重要
在一千万键、每个值 1 KiB 的测试中,Lavik 在 320 个连接下达到 803,016 GET QPS,p99 为 0.959 ms。连接数增加到 640 时,吞吐量达到 1,012,180 QPS,p99 升至 3.599 ms。再增加到 1,280 个连接,吞吐量降至 940,713 QPS,p99 则达到 9.791 ms。最后一个点中,更多在途请求没有换来更高吞吐量。这些测量揭示了一个不利的运行点,但没有单独确定其内部成因。
平均延迟也有助于解释连接数的含义。pipeline=1 时,每个连接最多有一个在途请求。在 640 连接的测量点,每秒约 101.2 万次请求乘以 0.632 ms 平均延迟,得到约 640 个在途请求。在 1,280 连接的测量点,每秒约 94.1 万次请求乘以 1.360 ms,得到约 1,279 个。这个算式描述了测量中的在途请求数量;不能用 p99 替代其中的平均延迟。
假设以 1 ms 为预算,选择就会改变
假设筛选条件是客户端观测到的 GET p99 低于 1 ms。这只是示例条件,并非 SLA 承诺。在提供的一千万键测量中,Lavik 满足条件的最高吞吐量为 803,016 QPS,对应 320 个连接,p99 为 0.959 ms。Redis 8.8.0 为 769,130 QPS,使用 16 个 I/O 线程、320 个连接,p99 为 0.975 ms。Valkey 9.1.0 为 692,905 QPS,使用 16 个 I/O 线程、160 个连接,p99 为 0.519 ms。每个结果都是该产品所有已提供配置中满足条件的最佳实测点,没有通过插值推算极限。
对于 Valkey,这个筛选条件仍然选中报告峰值 GET 比较所用的 16 个 I/O 线程配置,但将选中的连接数从 1,280 改为 160。它还揭示了不同延迟分位之间的取舍:上述三个合格点的 p99.9 分别为 Lavik 2.023 ms、Redis 1.663 ms、Valkey 0.815 ms。满足 p99 门槛,并不意味着 p99.9 也有相同表现。此外,Lavik 的 0.959 ms p99 距离示例门槛很近;单次 30 秒观测无法证明运行时有可靠的余量。
数字必须与工作负载一起阅读
服务端采用 AMD EPYC 9V74,拥有 8 个物理核心、16 个逻辑 CPU、约 126 GiB 内存和六块 NVMe 设备,客户端位于另一台主机。一千万键组采用均匀随机 GET 或覆盖式 SET,值大小为 1,024 字节,pipeline=1,不限速,在 80–1,280 个连接之间扫描,每个窗口为 30 秒。默认的相关客户端随机种子也是测试范围的一部分。这是由并发数驱动的扫描,无法据此确定服务在外部独立施加的请求到达率或突发流量下,能否维持延迟目标。
独立的十亿键组采用 60 秒窗口、各客户端独立的随机种子,并将连接数扫描扩展到 2,560。Lavik 的 GET 峰值为 952,560 QPS,对应 640 个连接,p99 为 3.599 ms。这个 p99 与小数据集中的数字相同,并不意味着两种工作负载可以互换。写入结果进一步说明为什么要同时保留两个指标:Lavik 的 SET 峰值为 764,939 QPS,p99 为 10.879 ms;Garnet 2.1.5 的峰值为 751,057 QPS,p99 为 5.535 ms。两者都对应 1,280 个连接。只看吞吐量排名,会隐藏这一写入尾延迟差异。
报告包含 22 个新测量的 Lavik SPDK 数据点,以及从相同主机上较早一次扫描复用的 124 个对照产品数据点。各产品依次运行,每种命令、连接数和线程配置只测量一次。缓存策略、预热、内存预留和持久化设置存在差异;Redis 和 Valkey 关闭了 AOF 与自动快照。所有正式测量点均报告零连接错误,实测 GET 均无未命中,但 Garnet 测试后的精确键数和值长度检查尚未验证。这些检查和短时间窗口无法证明相同的崩溃持久性或 SLA。
把连接数扫描变成评估决策
开展自己的评估时,先明确所需吞吐量、p99 与 p99.9 预算,以及可接受的故障语义,再选择并发数。随后用有代表性的值大小、访问偏斜、命令组合和流水线深度进行连接数扫描。加入持续覆盖写入和后台空间回收,并在候选运行点附近重复测量。决策依据应是该运行点在实际流量下是否仍有余量;这里的结果可以作为这项工作的初始假设。
写入成功的响应并不代表同步 fsync 已完成。请按实际工作负载评估持久性、复制和恢复要求。
可以先用下面的 basic-commands 操作配方检查本地安装,再进行应用所需的兼容性与负载评估。该配方使用的存储路径与 SPDK 基准不同,不能复现其性能。Linux、io_uring 和 glibc 要求请参阅“Platform and packaging(平台与软件包)”。规划兼容性检查时,也可以配合阅读已有文章“Evaluate the Redis client’s second attempt(评估 Redis 客户端的第二次尝试)”。
读写第一组键
先安装所选 Linux 软件包和 redis-cli(Ubuntu/Debian:sudo apt-get install redis-tools)。适用于 Minimal 和 Standard。在终端 1 进入解压后包含 ./lavik 的软件包目录,再粘贴下面的 Bash 脚本并保持运行。在终端 2 执行客户端命令。启动 Lavik 无需修改 PATH、使用 root 权限或配置 systemd 服务。
set -euo pipefail
if [ ! -x ./lavik ]; then
echo "Run this from the extracted Lavik package directory (the folder containing ./lavik)." >&2
exit 1
fi
mkdir -p /tmp/lavik-example
if [ ! -e /tmp/lavik-example/data ]; then
fallocate -l 512M /tmp/lavik-example/data
fi
./lavik --bind 127.0.0.1 --port 6379 --metrics-port 0 \
--network kernel --storage uring \
--threads 2 --no-pin-workers --registered-buffer-mb-per-worker 64 \
--shutdown-checkpoint --data-file /tmp/lavik-example/data \
--log-dir /tmp/lavik-example/logs$ redis-cli -h 127.0.0.1 -p 6379 --raw PING
PONG
$ redis-cli -h 127.0.0.1 -p 6379 --raw SET greeting 'hello from Lavik'
OK
$ redis-cli -h 127.0.0.1 -p 6379 --raw GET greeting
hello from Lavik
$ redis-cli -h 127.0.0.1 -p 6379 --raw HSET user:42 name Ada
1
$ redis-cli -h 127.0.0.1 -p 6379 --raw HGET user:42 name
Ada✓ 查看实际执行记录
0.1.0-beta.1 · Linux-7.0.12-linuxkit-aarch64-with-glibc2.39 · 2026-09-30
此记录验证功能行为,不验证 NVMe 性能、断电持久性或 SLA。
{
"recipeId": "basic-commands",
"platform": "Linux-7.0.12-linuxkit-aarch64-with-glibc2.39",
"steps": [
{
"argv": [
"redis-cli",
"-h",
"127.0.0.1",
"-p",
"6379",
"--raw",
"PING"
],
"expected": "PONG",
"actual": "PONG"
},
{
"argv": [
"redis-cli",
"-h",
"127.0.0.1",
"-p",
"6379",
"--raw",
"SET",
"greeting",
"hello from Lavik"
],
"expected": "OK",
"actual": "OK"
},
{
"argv": [
"redis-cli",
"-h",
"127.0.0.1",
"-p",
"6379",
"--raw",
"GET",
"greeting"
],
"expected": "hello from Lavik",
"actual": "hello from Lavik"
},
{
"argv": [
"redis-cli",
"-h",
"127.0.0.1",
"-p",
"6379",
"--raw",
"HSET",
"user:42",
"name",
"Ada"
],
"expected": "1",
"actual": "1"
},
{
"argv": [
"redis-cli",
"-h",
"127.0.0.1",
"-p",
"6379",
"--raw",
"HGET",
"user:42",
"name"
],
"expected": "Ada",
"actual": "Ada"
}
],
"status": "passed",
"binaryVersion": "lavik 0.1.0-beta.1",
"executableOnPath": false,
"workingDirectory": "/opt/lavik",
"sourceCommit": "3955b98d43b312324aa8d52775df52cfb111c0d0",
"packageVersion": "v0.1.0-beta.1",
"gracefulRestart": "passed"
}