LAVIK 0.1.0 · 2026-10-13

小型实例上的 Lavik、Redis 与 Valkey 对比

在 2 vCPU、16 GiB 内存的虚拟机上,对比 Lavik、Redis 8.10.2 与 Valkey 9.1.2 的 100 万键表现,再测试 Lavik 在同一实例上通过 NVMe SSD 承载 1 亿键的能力。

Azure L2aos_v5 · 2 vCPU / 1 个物理核 · 标称 16 GiB 内存 · 1 KiB 值 · SPDK · pipeline 1 · 每点 30 秒

95.37 GiB16 GiB 虚拟机上的值负载
215,4631 亿键 GET 峰值 ops/s
195,8791 亿键 SET 峰值 ops/s

键数扩大 100 倍后,Lavik 的 GET 峰值吞吐量基本不变,SET 保留 95.2%;95.37 GiB 的值负载约为虚拟机标称内存的六倍。

Valkey 的 100 万键 GET 峰值最高,为 226,514 ops/s。Lavik 更高的 SET 峰值使用 640 个连接;在 160 个连接时,Valkey 的 SET 吞吐量略高且 p99.9 更低。保留率比较各自选定的峰值;Redis 和 Valkey 未测试 1 亿键。

从环境到结果:复现路线

  1. 准备 2 vCPU 服务端与独立 16 核客户端,安装指定发布版及 memtier 2.5.1,记录原始主机设置。
  2. 为 SPDK 准备一个已核验的空 NVMe 命名空间及 2 GiB 大页,独立加载 Lavik 的 100 万键和 1 亿键数据集。
  3. 逐一测试 Redis、Valkey 的 1 和 2 个 I/O 线程配置,每次从对应产品的基线 RDB 恢复。
  4. 核验精确键数与抽样长度,预热 10 秒,在 16–1280 个连接下测量 GET 和 SET;确认无未命中、无连接错误,并恢复主机及设备设置。

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

测试日期:2026-10-13。四条曲线均在同一台 Azure L2aos_v5 上测量:Lavik 100 万条、Lavik 1 亿条、Redis 8.10.2 100 万条、Valkey 9.1.2 100 万条;每条 value 为 1 KiB。

Lavik 已完整装入 100,000,000 条数据,逻辑 value 共 95.37 GiB,约为机器标称 16 GiB 内存的六倍。 测试前后精确 DBSIZE 一致,所有正式 GET 测试零 miss,全部 84 个测试点通过离线校验。数据量扩大 100 倍后,GET 峰值基本不变,SET 峰值保留 95.2%。

吞吐量与尾延迟

Lavik 1 亿条的峰值 GET / SET 分别为 215,463 / 195,879 ops/s,分别是其 100 万条峰值的 100.0% / 95.2%。下表是各自峰值,负载点不同;同并发比较见后表。

操作产品 / key 数worker 或 IO 线程峰值连接数峰值 ops/s
GETLavik 0.1.0 / 1M2160215,521
GETLavik 0.1.0 / 100M2320215,463
GETRedis 8.10.2 / 1M2640199,444
GETValkey 9.1.2 / 1M2160226,514
SETLavik 0.1.0 / 1M2640205,820
SETLavik 0.1.0 / 100M2640195,879
SETRedis 8.10.2 / 1M2320176,491
SETValkey 9.1.2 / 1M2160193,305

Redis / Valkey 各测 IO=1 和 IO=2;每个操作选峰值较高的一组 IO 设置,不逐点拼接。Lavik 两组均为 2 worker。

在 100 万条数据下,Lavik 的 GET 峰值比 Redis 高 8.1%,SET 峰值比 Redis 高 16.6%、比 Valkey 高 6.5%。Valkey 的 GET 峰值为 226,514 ops/s,是三者中最高的。Lavik 扩展到 1 亿条后,GET / SET 峰值仍达到 215,463 / 195,879 ops/s。

同为 160 连接时,Lavik 100 万条的 SET 为 191,909 ops/s、p99.9 为 3.727 ms;Valkey 为 193,305 ops/s、p99.9 为 1.279 ms。Lavik 在 640 连接达到更高的 SET 峰值;各连接数下的吞吐量和尾延迟见下表。

主图展示 16–640 连接;包含 1280 连接的完整测量结果保留在下方表格和全部配置图中。

GET and SET throughput and p99.9 latency打开完整图表 ↗

同并发比较

单元格为 ops/s / p99.9 ms,线程设置与主图一致。

操作连接数Lavik 0.1.0 / 1MLavik 0.1.0 / 100MRedis 8.10.2 / 1MValkey 9.1.2 / 1M
GET1627,000 / 1.02327,350 / 0.88728,782 / 0.83130,119 / 0.879
GET3254,045 / 1.14354,040 / 1.18358,729 / 1.15161,175 / 0.719
GET80133,263 / 0.935132,749 / 1.039138,448 / 0.967148,442 / 0.927
GET160215,521 / 1.239215,434 / 1.215192,381 / 1.239226,514 / 1.231
GET320215,506 / 2.527215,463 / 2.495196,845 / 2.415217,955 / 2.255
GET640215,515 / 6.207215,341 / 6.175199,444 / 6.815220,110 / 6.079
GET1280212,841 / 14.207206,966 / 15.167197,000 / 15.871214,338 / 14.591
SET1630,588 / 1.19130,046 / 0.91128,968 / 0.90329,855 / 1.119
SET3260,089 / 1.51159,590 / 1.63159,436 / 0.65560,230 / 1.055
SET80134,454 / 2.575133,977 / 3.423138,636 / 0.895146,877 / 0.951
SET160191,909 / 3.727182,929 / 4.639175,288 / 1.263193,305 / 1.279
SET320204,388 / 5.407194,731 / 6.655176,491 / 2.863188,953 / 3.631
SET640205,820 / 9.279195,879 / 10.495168,555 / 8.383188,035 / 6.847
SET1280201,238 / 18.175184,738 / 27.007166,281 / 18.175180,500 / 14.399

完整 IO 线程扫描

单元格为峰值 ops/s(该点连接数)。原始 84 点均保留。

产品IO threadsGETSET
Redis 8.10.2 / 1M1176,390 (1280)167,913 (1280)
Redis 8.10.2 / 1M2199,444 (640)176,491 (320)
Valkey 9.1.2 / 1M1173,638 (1280)169,158 (1280)
Valkey 9.1.2 / 1M2226,514 (160)193,305 (160)

All six configurations打开完整图表 ↗

内存与客户端诊断

产品 / key 数INFO used_memory (MiB)Process RSS (MiB)
Lavik 0.1.0 / 1M98.0–98.0740.4–787.2
Lavik 0.1.0 / 100M5,303.5–5,303.55,697.3–5,733.6
Redis 8.10.2 / 1M1,264.8–1,265.01,288.0–2,267.8
Valkey 9.1.2 / 1M1,268.1–1,268.61,290.6–2,245.6

表中范围来自正式测试点结束后的采样。INFO used_memory 为各产品自身的内存统计,Process RSS 为操作系统记录的进程驻留内存;Lavik 的主机内存开销还包括 2,048 MiB hugepages 预留。客户端最高消耗 3.345 个 CPU 核(共 16 个物理核心),服务端采样窗口内最高 CPU steal 为 0.0000%。

版本与硬件

测试使用截至 2026-10-13 的最新稳定版 Redis 8.10.2、Valkey 9.1.2,以及官方标准 x86_64 Lavik v0.1.0 二进制。Lavik 源码提交为 feb61224e414c5653fd09560e20c9d214db1e3ac。

项目配置
服务端一台 Standard_L2aos_v5,East US 第 3 可用区;标称 2 vCPU / 16 GiB,Linux 可见内存 15.62 GiB
CPUAMD EPYC 9V45;一个物理核心的两个 SMT 线程;所有配置均限制在 CPU 0–1
客户端同可用区独立 Standard_F16as_v7;AMD EPYC 9V45,16 个物理核心;memtier 绑定 CPU 0–15
操作系统两端 Ubuntu 24.04,Linux 6.17.0-1022-azure
网络同私有 VNet、同可用区,MANA VF 加速网络,内核 TCP
Lavik 存储独占 1,439,887,785,984 字节本地 NVMe namespace;SPDK/VFIO;临时预留 2 GiB hugepages
Redis / Valkey 存储数据集位于内存,关闭 AOF 与自动快照;基线 RDB 仅用于配置之间恢复数据
系统策略THP never、overcommit 1、无 swap、无额外 cgroup 内存限制;网卡中断共享服务端 CPU

所有配置在同一台服务端 VM 上依次运行,使用相同的两个 vCPU。Lavik 通过 SPDK 使用本地 NVMe,Redis 和 Valkey 使用内存保存数据。

测试方法

四条曲线统一使用独立客户端随机种子(--distinct-client-seed),在各自完整数据集范围内随机访问。

  • 十进制 key 为 1..N,无前缀,value 为 1,024 字节,无 TTL。三条基线曲线 N=1,000,000,新增 Lavik 曲线 N=100,000,000。每个数据集均从空服务端或清空后的测试 namespace 独立加载。
  • memtier 2.5.1,16 个客户端线程,pipeline=1,不限速,独立客户端随机种子,在完整声明范围内均匀随机 GET 或覆盖 SET。总连接数为 16/32/80/160/320/640/1280。每点 30 秒,每个服务端配置先进行 10 秒 GET 预热。
  • 顺序为 Lavik 100 万、Lavik 1 亿、Redis IO=1/2、Valkey IO=1/2;每组先 GET 后 SET,并发递增。编译与测量不重叠。六组配置合计 84 个正式测试点,每点一轮。
  • Lavik 使用两个绑定 CPU 的数据 worker、内核 TCP 与 SPDK;busy-poll 20 µs,前后台预算 1000/10 µs,后台保障 1%,SPDK completion cap 16、前台 pre-poll 5 µs;关闭 metrics 监听。
  • 加载、预热、测量期间均开启 defrag:defrag-paused=no,每设备 max active=8,block/record sleep=0。Tomb Raider interval=0,关闭的是墓碑扫描,不是 defrag。每个正式测试点前后均记录状态。
  • 每个 Lavik 数据集前均校验临时 NVMe 的序列号、PCI 地址、namespace、容量、挂载状态、分区、holders 和使用进程,再 discard。正常退出后恢复内核 NVMe 驱动与 hugepage/VFIO 设置,然后执行下一组配置。
  • Redis 和 Valkey 从固定 release 提交以默认优化选项编译,使用 jemalloc 5.3.0;设置 save ""、appendonly no、maxmemory 0。每个产品初次加载后,在正式计时窗口之外手动 SAVE 一次生成基线 RDB,各 IO 配置重载该基线。计时写入为纯内存写入。
  • 每个配置前后核验精确 DBSIZE 与五个抽样 value 长度。检查加载操作数、GET 命中数、连接错误、运行时版本与 CPU 绑定。

同一台小机器,承载 100 倍数据

Lavik 在内存中保存索引,将 value 存放于 NVMe。本次使用相同的 2 vCPU / 16 GiB 配置,把数据集从 100 万条扩展到 1 亿条,装入 95.37 GiB 逻辑 value 数据,约为机器标称内存的六倍;key 和元数据另计。

数据量扩大 100 倍后,GET 峰值从 215,521 变为 215,463 ops/s,基本保持不变;SET 峰值从 205,820 变为 195,879 ops/s,保留了 95.2%。测试前后完整 key 数均为 100,000,000,所有正式 GET 测试零 miss。

三条 100 万条曲线展示 Lavik 与 Redis、Valkey 在小机器上的性能;Lavik 1 亿条曲线进一步展示同样硬件承载更大数据集的能力。在这组测试中,Lavik 将数据容量扩展到内存之外,同时保持了接近小数据集的峰值吞吐量。

复现命令

服务端使用上述 L2aos_v5,客户端使用同可用区的 F16as_v7;两端通过私网连接。准备 Lavik v0.1.0 标准 x86_64 二进制、Redis 8.10.2、Valkey 9.1.2 和 memtier 2.5.1。Redis / Valkey 使用默认优化构建、jemalloc 5.3.0,编译参数为 make -j12 BUILD_TLS=yes。以下命令使用 Bash。

启动服务端

Bash
# Run on the server; replace the address and binary paths for your hosts.
SERVER_IP=10.93.0.7
LAVIK_BIN=/opt/lavik-v0.1.0-linux-x86_64/lavik
REDIS_BIN=/opt/redis-8.10.2/src/redis-server
VALKEY_BIN=/opt/valkey-9.1.2/src/valkey-server
ulimit -n 65535
sudo sysctl -w vm.overcommit_memory=1
printf 'never\n' | sudo tee /sys/kernel/mm/transparent_hugepage/enabled

Lavik 使用空的专用 NVMe namespace。启动前按 SPDK 存储指南 识别测试盘并绑定 VFIO,本测试设置 HUGEMEM=2048、BYCORF_DPDK_MEMORY_MB=2048。该 VM 未暴露 IOMMU,按指南使用了专用测试机的 no-IOMMU 配置。分别加载 100 万条和 1 亿条;两次之间正常退出 Lavik、恢复 NVMe 驱动,再清空已确认的测试 namespace 并重新绑定,独立加载数据。

Bash
# Use the verified PCI address and namespace ID of your empty test NVMe.
NVME_BDF=YOUR_NVME_PCI_ADDRESS
NVME_NSID=1
sudo prlimit --memlock=unlimited:unlimited --nofile=65535:65535 \
  env BYCORF_EAL_ARGS="-a ${NVME_BDF}" BYCORF_DPDK_MEMORY_MB=2048 \
  taskset -c 0-1 "$LAVIK_BIN" \
  --storage=spdk --bind="$SERVER_IP" \
  --tomb-raider-interval-ms=0 --spdk-max-completions-per-poll=16 \
  --log-dir="$PWD/lavik-logs" \
  --data-file="spdk://${NVME_BDF}/${NVME_NSID}"

以上省略 v0.1.0 的默认参数;taskset -c 0-1 下默认启动两个绑定 CPU 的 worker。BYCORF_DPDK_MEMORY_MB=2048 设置 SPDK 使用的 EAL 内存为 2 GiB,网络仍为默认的内核 TCP。保留的调优项将 tombstone 扫描间隔从默认一天改为关闭、SPDK completion cap 从默认 8 改为 16。

Redis 和 Valkey 分别测试 1、2 个 I/O 线程,每次只运行一个服务端:

Bash
# Run one product/configuration at a time. Repeat with IO_THREADS=2.
IO_THREADS=1
mkdir -p "$PWD/bench-data/redis" "$PWD/bench-data/valkey"

# Redis 8.10.2
taskset -c 0-1 "$REDIS_BIN" \
  --bind "$SERVER_IP" --port 6379 --protected-mode no --daemonize no \
  --dir "$PWD/bench-data/redis" --dbfilename dump.rdb \
  --save "" --appendonly no --maxmemory 0 --maxclients 10000 \
  --io-threads "$IO_THREADS"

# Valkey 9.1.2 (after stopping Redis)
taskset -c 0-1 "$VALKEY_BIN" \
  --bind "$SERVER_IP" --port 6379 --protected-mode no --daemonize no \
  --dir "$PWD/bench-data/valkey" --dbfilename dump.rdb \
  --save "" --appendonly no --maxmemory 0 --maxclients 10000 \
  --io-threads "$IO_THREADS"

客户端灌数与测试

按 Lavik 100 万、Lavik 1 亿、Redis IO=1/2、Valkey IO=1/2 的顺序运行。切换配置时修改 N 和 RUN。每个产品首次灌数前确认 DBSIZE=0。Redis / Valkey 首次灌数后执行一次 SAVE,再用 SHUTDOWN NOSAVE 退出;每组 I/O 线程配置都从同一基线 RDB 重启,跳过再次灌数,重新检查数据后执行预热与扫描。Lavik 两组均从空的测试 namespace 独立灌数。

Bash
# Bash, on the separate 16-core client; memtier_benchmark 2.5.1.
set -euo pipefail
SERVER_IP=10.93.0.7
N=1000000                 # Use 100000000 for the large Lavik dataset.
RUN=lavik-1m              # lavik-100m, redis-io1, redis-io2, valkey-io1, valkey-io2
mkdir -p "$RUN"
ulimit -n 65535
COMMON=(--server="$SERVER_IP" --port=6379 --protocol=redis \
  --threads=16 --pipeline=1 --key-minimum=1 --key-maximum="$N" \
  --key-prefix= --data-size=1024 --hide-histogram \
  --print-percentiles=50,99,99.9,99.99 --show-config)

# Fill an empty dataset using disjoint sequential key streams: exactly N writes.
taskset -c 0-15 memtier_benchmark "${COMMON[@]}" --clients=10 \
  --requests="$((N / 160))" --ratio=1:0 --key-pattern=P:P \
  --json-out-file="$RUN/fill.json" > "$RUN/fill.txt" 2>&1

check_data() {
  test "$(redis-cli -h "$SERVER_IP" --raw DBSIZE)" = "$N"
  for key in 1 2 "$((N / 2))" "$((N - 1))" "$N"; do
    test "$(redis-cli -h "$SERVER_IP" --raw STRLEN "$key")" = 1024
  done
}
check_data

# For each memory peer only: create its baseline RDB once after the first fill.
# redis-cli -h "$SERVER_IP" SAVE

# Warmup, then GET and SET sweeps. Reuse this part after loading a baseline RDB.
taskset -c 0-15 memtier_benchmark "${COMMON[@]}" --clients=10 \
  --distinct-client-seed --test-time=10 \
  --command='GET __key__' --command-key-pattern=R --command-is-read \
  --json-out-file="$RUN/warmup.json" > "$RUN/warmup.txt" 2>&1

for kind in GET SET; do
  if [ "$kind" = GET ]; then
    COMMAND='GET __key__'; READ_FLAG=(--command-is-read)
  else
    COMMAND='SET __key__ __data__'; READ_FLAG=()
  fi
  for connections in 16 32 80 160 320 640 1280; do
    taskset -c 0-15 memtier_benchmark "${COMMON[@]}" \
      --clients="$((connections / 16))" --distinct-client-seed --test-time=30 \
      --command="$COMMAND" --command-key-pattern=R "${READ_FLAG[@]}" \
      --json-out-file="$RUN/${kind}-c${connections}.json" \
      > "$RUN/${kind}-c${connections}.txt" 2>&1
  done
done
check_data

每组结束后检查 GET 全部命中、连接错误为零,且 DBSIZE 保持不变。