LAVIK 0.1.0 · 2026-10-13

高吞吐量场景下的 Lavik、Redis 与 Valkey 对比

使用 16 vCPU 虚拟机,在 1,000 万键下对比 NVMe SSD 上的 Lavik 0.1.0 SPDK 与 DRAM 上的 Redis 8.10.2、Valkey 9.1.2,并独立测试 Lavik 的十亿键容量。

Azure L16aos_v4 · 16 个逻辑 CPU / 8 个物理核 · 六块 NVMe SSD · 1 KiB 值 · pipeline 1 · 120 个正式测试点

1,145.8k1,000 万键 GET QPS · p99.9 7.135 ms
972.0k1,000 万键 SET QPS · p99.9 7.199 ms
1,069,126十亿键 GET QPS · 640 个连接

在 1,000 万键下,Lavik 达到 1,145.8k GET 峰值 QPS 和 972.0k SET 峰值 QPS。独立的十亿键测试在 NVMe SSD 上达到 1,069,126 GET QPS。

1,000 万键组使用 30 秒窗口和默认相关种子;仅 Lavik 的十亿键组使用 60 秒和独立种子。内存对照关闭 AOF 与定时快照。每点仅测一次,比较时需同时考虑持久化和负载条件。

从环境到结果:复现路线

  1. 准备两台测试虚拟机、Lavik 0.1.0 Standard 发布包、指定 Redis/Valkey 版本及 memtier 2.5.1。
  2. 核验六块空测试 NVMe,绑定至 SPDK 并预留 8 GiB 大页;分别独立加载 1,000 万键与十亿键 Lavik 数据集,保持碎片整理开启。
  3. 使用 jemalloc 5.3.0 构建内存对照。每个产品保存一份基线 RDB,关闭 AOF、定时快照后测试 1/2/4/8/16 个 I/O 线程。
  4. 按对应数据集的时长、种子与预热策略执行 80–1280 个连接测试,核验键数、值长度、未命中及错误。

下方完整报告提供发布版本、主机条件、服务端配置、加载、预热、测量与验证命令。路径、地址和 PCI 标识来自测试环境,使用前须替换。格式化或清空仅适用于确认无数据的专用测试设备。

测试日期:2026-10-13。使用下载的 Lavik v0.1.0 标准 x86_64 正式包,重新测量 Redis 8.10.2、Valkey 9.1.2 对照。本报告的 120 个正式测量点包含 110 个 1,000 万 key 对比点,以及 10 个 Lavik 单独运行的 10 亿 key 点;每条 value 均为 1,024 B。Lavik 在灌入、预热、测量期间始终开启 defrag;没有复用历史对照数据。

10M 组的 Lavik GET 峰值为 1145.8k QPS(1280 连接,p99.9 7.135 ms);SET 峰值为 972.0k QPS(1280 连接,p99.9 7.199 ms)。这些是单次测量结果。图中展示吞吐和 p99.9 延迟随连接数的变化。

吞吐与 p99.9 延迟对比:Lavik 10M / 1B 与 Redis / Valkey

GET and SET throughput and p99.9 latency打开完整图表 ↗ 四组均展示 80–1280 连接。10M 组每点 30 秒,使用默认客户端随机种子;1B 组每点 60 秒,使用独立客户端随机种子。数据规模、时长及随机种子设置存在差异。

延迟为客户端完整测量窗口的 p99.9,单位为毫秒,越低越好。使用与吞吐图相同的 I/O 线程配置;横轴为 log₂ 连接数刻度。

1B GET 峰值为 1,069,126 QPS(640 连接),SET 峰值为 817,113 QPS(1280 连接)。这些仅描述本次单轮扫描。

整批正式测量采集时间(含每点准备/快照开销):2026-10-13 11:33:05 UTC – 2026-10-13 13:12:17 UTC

硬件与部署

项目配置
服务端Azure Standard_L16aos_v4,Japan West / zone 2;AMD EPYC 9V74;16 逻辑核 / 8 物理核;Linux 内存 125.8 GiB
客户端Azure Standard_F32als_v6;AMD EPYC 9V74;32 物理核;Linux 内存 62.8 GiB;压测进程限制到 CPU 0–15
系统两端均为 Ubuntu 24.04、Linux 6.17.0-1022-azure;本批未重启
网络客户端 10.84.0.5 → 服务端 10.84.1.4:6379;mlx5 加速路径;kernel TCP
服务端 CPU全产品使用 CPU 0–15;Lavik 16 workers;Redis/Valkey 1/2/4/8/16 I/O 线程
Lavik 存储六张专用本地 NVMe,每张 1,919,850,381,312 B;SPDK/VFIO;8 GiB hugepages
Redis/Valkey 存储数据驻 DRAM;关闭 AOF、自动快照;校验后的 RDB 仅用于各线程配置间重置基线

方法与边界

  • 本报告覆盖 Redis/Valkey 10M 组与 Lavik 1B 存储组。对照版本为 2026-10-13 选择的最新稳定版:Redis 8.10.2、Valkey 9.1.2。
  • 顺序为 Lavik SPDK 10M → Lavik SPDK 1B → Redis → Valkey。任一时刻只运行一个被测数据库和一个压测客户端;本组不改变服务端 CPU/IRQ 策略。编译在正式测量前完成。
  • 使用 lavik-v0.1.0-linux-x86_64.tar.gz;源码 feb61224e414c5653fd09560e20c9d214db1e3ac;可执行文件 SHA-256 7bf8d204a137678cee0e1a621b02661181373fbbfe1366bc1c201942b9dcf420;GCC 13 Release / x86-64-v2。未重新编译 Lavik,也未添加源码补丁。压缩包校验与发布的 SHA-256 一致。
  • Lavik 使用 spdk-max-completions-per-poll=16(v0.1.0 默认 8)。Defrag 保持开启,每个正式点前后均核验。
  • 清盘前按序列号、PCI、namespace、容量核验六张临时盘,排除系统盘和开发盘。分别独立灌入 10M 与 1B Lavik 数据集,两次灌入之间重新清理六张临时盘;Lavik 干净停止后,盘交还内核并释放 hugepage 预留。
  • Redis/Valkey 使用发布的 8.10.2 / 9.1.2 固定 commit 编译,分配器为 jemalloc 5.3.0。各自新灌数据并生成 SHA 校验的 RDB;每档 I/O 线程从该基线启动。AOF、自动快照关闭,其他配置保留产品默认值。
  • 10M 组:memtier 2.5.1,16 线程、pipeline=1、不限速;十进制 key 1..10,000,000、无前缀;均匀随机 GET 或覆盖 SET;value=1,024 B。连接数 80/160/320/640/1280,每点 30 秒,每档服务端配置先 GET 预热 10 秒。按原 10M 方法保留默认相关客户端随机种子。
  • 1B 组使用十进制 key 1..1,000,000,000、均匀随机 GET 或覆盖 SET;80/160/320/640/1280 连接,每点 60 秒,各客户端使用 distinct seed,不额外 GET 预热;沿用原报告存储组方法,并启用 shutdown checkpoint。1.024 TB 为 value 原始载荷,不包含 key、索引与存储格式开销。
  • 每档服务端配置前后核验精确 DBSIZE 和五个样本 value 长度。全部正式 GET miss=0,全部正式点连接错误=0。延迟为完整测量窗口的客户端分位数;
  • 每点仅测一次,未估计置信区间或运行间波动;产品按顺序而非交错执行。线程职责不同,Lavik 持久化与纯内存对照的保证也不同。此次客户端型号、CPU 以及 Lavik 构建与历史报告存在差异,不能将跨报告的变化全部归因于 Lavik 代码。

Redis / Valkey 持久化配置

周期性 RDB 和 AOF 在灌入、预热及正式测量期间均关闭。配置文件:redis-valkey-persistence.conf。

配置
save ""
appendonly no

每个产品首次灌入 10M 数据后,仅在正式窗口之外手动执行一次 SAVE,供各 I/O 线程配置重启恢复同一基线。启动时读取该 RDB 不会开启周期性快照。正式测量期间写入仅在内存中完成。

运行命令

以下 Bash 命令对应实测参数;请按部署环境调整路径、地址和 PCI 身份。一次只运行一个数据库和一个压测客户端。每轮使用独立结果目录。

Lavik SPDK

按 SPDK 存储指南核验六张专用临时 NVMe、准备 VFIO 和 8 GiB hugepages。10M 和 1B 两组各从空盘独立灌入;不得覆盖系统盘或已有业务数据。完成后干净停止 Lavik、恢复内核驱动及 hugepage 预留,再测试内存数据库。以下为 10M 启动命令:

Bash
sudo env 'BYCORF_EAL_ARGS=-a 2401:00:00.0 -a 221c:00:00.0 -a 7f10:00:00.0 -a 399c:00:00.0 -a c0ef:00:00.0 -a 6319:00:00.0' BYCORF_DPDK_MEMORY_MB=8192 taskset \
  -c 0-15 /mnt/dev/benchmark/release010/downloads/lavik-v0.1.0-linux-x86_64/lavik \
  --storage=spdk \
  --bind=10.84.1.4 \
  --threads=16 \
  --tomb-raider-interval-ms=0 \
  --spdk-max-completions-per-poll=16 \
  --log-dir=/mnt/dev/benchmark/release010-latest/suite/memory/lavik-spdk/logs \
  --data-file=spdk://2401:00:00.0/1 \
  --data-file=spdk://221c:00:00.0/1 \
  --data-file=spdk://7f10:00:00.0/1 \
  --data-file=spdk://399c:00:00.0/1 \
  --data-file=spdk://c0ef:00:00.0/1 \
  --data-file=spdk://6319:00:00.0/1

1B 组使用相同参数,增加 --shutdown-checkpoint,并将日志目录改为 suite/storage/lavik-spdk/logs。启动后等待恢复及后台初始化完成,使用 CONFIG GET defrag-paused 核验为 no;各正式点前后再次核验。

Redis / Valkey

使用 jemalloc 5.3.0,构建命令为 make -j16 BUILD_TLS=no。Redis 8.10.2 对应提交 498ecd0d6d007db11ddb3aea9428552598a78622,Valkey 9.1.2 对应 7f1dffedff6de73058b2c2a389422b6ecd56c8fb。首次灌入使用 16 I/O 线程。

Bash
# On the server; start one product/configuration at a time.
PRODUCT=redis                    # redis or valkey
IO_THREADS=16                    # tested: 1, 2, 4, 8, 16
DATA_DIR=/mnt/dev/benchmark/release010-latest/suite/data/$PRODUCT
mkdir -p "$DATA_DIR"
ulimit -n 65535
taskset -c 0-15 "/mnt/dev/benchmark/release010-latest/downloads/$PRODUCT/src/$PRODUCT-server" \
  --bind 10.84.1.4 --protected-mode no --port 6379 --daemonize no \
  --dir "$DATA_DIR" --dbfilename dump.rdb --save "" --appendonly no \
  --maxmemory 0 --maxclients 10000 --io-threads "$IO_THREADS"

图中 Redis GET/SET 使用 16 I/O 线程,Valkey GET 使用 16、SET 使用 8。它们来自完整的 1/2/4/8/16 线程扫描,每个命令选择单个峰值最高的配置。

灌入、预热和连接数扫描

Bash
# On the client: memtier_benchmark 2.5.1.
KEYS=10000000                    # 1000000000 only for the Lavik 1B group
SECONDS_PER_POINT=30
SEED=()
if [ "$KEYS" -eq 1000000000 ]; then
  SECONDS_PER_POINT=60
  SEED=(--distinct-client-seed)
fi
mkdir -p results
ulimit -n 65535
COMMON=(--server=10.84.1.4 --port=6379 --protocol=redis
  --threads=16 --pipeline=1 --key-minimum=1 --key-maximum="$KEYS"
  --key-prefix= --data-size=1024 --hide-histogram
  --print-percentiles=50,99,99.9,99.99 --show-config)

# Once per fresh dataset; each key is written once across 640 clients.
taskset -c 0-15 memtier_benchmark "${COMMON[@]}" --clients=40 \
  --json-out-file=results/fill.json --requests="$((KEYS / 640))" \
  --ratio=1:0 --key-pattern=P:P
redis-cli -h 10.84.1.4 -p 6379 --raw DBSIZE
redis-cli -h 10.84.1.4 -p 6379 STRLEN 1

对于 Redis/Valkey,首次灌入后执行下列基线命令;然后以 1、2、4、8、16 I/O 线程依次启动,每档等待 RDB 加载结束、DBSIZE=10000000,核验持久化配置,再执行下一段预热及测量。每档结束使用 SHUTDOWN NOSAVE,保持同一 dump.rdb 不变;不要重新灌入或在测量中执行 SAVE。Lavik 不执行这些 RDB 基线命令。

Bash
redis-cli -h 10.84.1.4 -p 6379 SAVE
redis-cli -h 10.84.1.4 -p 6379 SHUTDOWN NOSAVE
# On the server, record/check the unchanged RDB baseline hash:
sha256sum "$DATA_DIR/dump.rdb"
# After each restart, run these checks from the client:
redis-cli -h 10.84.1.4 -p 6379 CONFIG GET save
redis-cli -h 10.84.1.4 -p 6379 CONFIG GET appendonly
redis-cli -h 10.84.1.4 -p 6379 INFO persistence
Bash
# Run once for Lavik 10M and once per Redis/Valkey I/O-thread configuration.
# The Lavik 1B group has no additional GET warmup.
if [ "$KEYS" -eq 10000000 ]; then
  taskset -c 0-15 memtier_benchmark "${COMMON[@]}" --clients=40 \
    --json-out-file=results/warmup.json --test-time=10 \
    --command='GET __key__' --command-key-pattern=R --command-is-read
fi
for OP in GET SET; do
  COMMAND=(--command='GET __key__' --command-key-pattern=R --command-is-read)
  if [ "$OP" = SET ]; then
    COMMAND=(--command='SET __key__ __data__' --command-key-pattern=R)
  fi
  for CONNECTIONS in 80 160 320 640 1280; do
    taskset -c 0-15 memtier_benchmark "${COMMON[@]}" \
      --clients="$((CONNECTIONS / 16))" \
      --json-out-file="results/$OP-c$CONNECTIONS.json" \
      --test-time="$SECONDS_PER_POINT" "${COMMAND[@]}" "${SEED[@]}" \
      > "results/$OP-c$CONNECTIONS.txt" 2>&1
  done
done
redis-cli -h 10.84.1.4 -p 6379 --raw DBSIZE