Garnet 基准测试指南:从 RESP 端到端压测到 BenchmarkDotNet 微基准与 Tsavorite 设备层
【免费下载链接】garnetGarnet is a remote cache-store from Microsoft Research that offers strong performance (throughput and latency), scalability, storage, recovery, cluster sharding, key migration, and replication features. Garnet can work with existing Redis clients.项目地址: https://gitcode.com/GitHub_Trending/garnet4/garnet
Garnet 仓库的benchmark/目录集中承载了面向不同层次的基准测试工具:面向完整 RESP 服务的Resp.benchmark(端到端吞吐量与延迟)、基于 BenchmarkDotNet 的BDN.benchmark(单命令 CPU/分配微基准),以及 Tsavorite 存储引擎层级的Device.benchmark(原始 IDevice IOPS)与KV.benchmark(引擎吞吐)。本文以 benchmark/README.md 为骨架,逐一讲解各工具的作用、关键参数、三大压测场景与可复现的 8×NVMe RAID-0 实测矩阵,帮助读者掌握从"纯内存命中"到"NVMe 随机读"再到"LocalMemory 软件上限"的完整压测方法论,以及如何用命令行一键复现仓库文档中的官方结果。
基准测试工具全景:四层压测栈
Garnet 的基准测试从下到上分为四层,每层都在上一层的基础上叠加每操作的额外工作:
- Device 层:
Device.benchmark直接测 TsavoriteIDevice后端的随机读 IOPS(Linux 上为 Native libaio / io_uring,Windows 上为 IOCP,另有 FileStream、RandomAccess 与纯内存的LocalMemory),用于确认后端能否打满原始 NVMe 上限。 - KV 层:
KV.benchmark通过 Tsavorite 引擎的BasicContext安全路径测量 load(插入)与 run(RUMD = 读 / 更新 / RMW / 删除)吞吐。 - Resp 层:
Resp.benchmark以完整 RESP 服务器为被测对象,驱动 GET/SET/INCR/MGET 等负载,是四层中的最顶层。 - fio 层:作为磁盘原始能力的参照物,用于划定设备层之上各层的理论上限。
四层的关系可以表达为Resp ≤ KV ≤ Device ≤ fio,每层都有对应的磁盘(NVMe storage-bound)与LocalMemory(内存)场景用于横向对比。需要注意,三层基准使用的数据集和配置各不相同,因此它们的绝对数值不能直接互相比较——比较时应关注各层相对其参照物(fio 或 LocalMemory)的百分比,而不是跨层数字本身。
Resp.benchmark:端到端吞吐与延迟压测
Resp.benchmark是面向完整 RESP 服务器的端到端基准工具,位于 benchmark/Resp.benchmark。它既可以通过 TCP 驱动任何 RESP 服务器(Garnet、Redis、KeyDB、Dragonfly),也可以驱动进程内嵌的 Garnet 服务器。其完整可选参数可通过dotnet $RB --help查看(参数定义见 Options.cs)。
两种测量模式
- 离线模式(Offline):
--op X,使用预构建的请求批次(大小为-b),N 个线程循环执行Send → CompletePending。报告吞吐量(ops/sec),用于打满服务器。离线模式默认使用LightClient——一个零分配的流水线客户端。 - 在线模式(Online):
--online,每个线程在途请求数为 1(--itp K可增加),每操作延迟通过 HdrHistogram 记录并每 2 秒打印一次。用于获取延迟曲线。在线模式默认使用GarnetClientSession(异步流水线客户端,支持--itp)。
可用客户端
客户端通过--client选择(枚举定义见 Common/ClientTypes.cs):
| 客户端 | 说明 |
|---|---|
LightClient | 离线模式默认,零分配流水线客户端 |
GarnetClientSession | 在线模式默认,异步流水线,支持--itp |
GarnetClient | Garnet 异步客户端 |
SERedis | StackExchange.Redis,用于与 Redis 做同类对比(apples-to-apples) |
InProc | 进程内嵌,无 TCP,只测服务器 CPU |
构建与运行
dotnet build benchmark/Resp.benchmark/Resp.benchmark.csproj -c Release -f net10.0 dotnet build main/GarnetServer/GarnetServer.csproj -c Release -f net10.0 RB=benchmark/Resp.benchmark/bin/Release/net10.0/Resp.benchmark.dll GS=main/GarnetServer/bin/Release/net10.0/GarnetServer.dll dotnet $GS --port 6379 & # 启动服务器 dotnet $RB --op GET --dbsize 1000000 -t 8 -b 512 --runtime 15 # 离线吞吐量务必在Release构建下测量。--op支持的操作类型定义在 Common/OpType.cs,涵盖字符串类(GET、SET、MSET、MGET、INCR、SETEX、DEL)、Bitmap 类(SETBIT、GETBIT、BITCOUNT、BITPOS、BITOP_AND/OR/XOR/NOT、BITFIELD 系列)、HyperLogLog 类(PFADD、PFCOUNT、PFMERGE)、有序集合与 Geo 类(ZADD、ZREM、ZCARD、GEOADD、ZADDCARD)、PubSub(PUBLISH、SPUBLISH)、事务与脚本类等。
关键参数
| 参数 | 默认值 | 作用 |
|---|---|---|
--op | GET | 离线下压测的操作:GET、MGET、INCR、SET、ZADD…… |
--dbsize | 1024 | 不同键的数量(除非使用-s,否则会预加载) |
--valuelength | 8 | 值字节数(使用--keylength 16 --valuelength 96可得到 128 B 记录,与 KV/Device 基准对齐) |
-t | 1,2,4,8,16,32 | 线程数扫描(离线) |
-b | 4096 | 每流水线的请求数(离线;吞吐量的主导旋钮,1024是较好的默认值;在线强制为 1) |
--runtime | 15 | 每个单元的运行秒数;0= 仅加载不运行 |
-s | false | 跳过加载,对已预加载的服务器运行 |
--itp | 1 | 在线模式:每线程在途请求数 |
--zipf | false | 使用偏斜键(θ=0.99)代替均匀分布,包括在每个--cluster-bench分片内部 |
另有--load-threads(初始数据加载阶段线程数,默认 8)、--auth(认证密码)、--tls/--tlshost/--cert-file-name/--cert-password(TLS 支持)、--cluster-bench(跨集群分片分发负载)、--replica-ops-percent(写操作按百分比路由副本)、--logger-level、--file-logger等参数,完整列表见 Options.cs。
两大阶段:加载与运行
工具分两个阶段工作:先加载键到数据库,再对单个命令运行基准。两个阶段都使用ReqGen生成请求,其构造函数(见 Resp.benchmark/OfflineBench/ReqGen.cs)暴露了以下核心参数:
public ReqGen( int Start, // 键偏移量 int DbSize, // 数据库中的键数量 int NumOps, // 要执行的操作总数 int BatchSize, // 一批中的操作数 OpType opType, // 要执行的操作(如 GET、MSET、INCR) bool randomGen = true, // 按键顺序还是随机生成 bool randomServe = true, // 随机还是按生成顺序服务请求 int keyLen = default, // 键的最小字节数;默认等于 DbSize 的最大位数 int valueLen = default) // 值的总字节数LoadData(loadDbThreads, BatchSize, keyLen, valueLen)用于把数据加载进数据库,Run(opType, TotalOps, NumThreads, BatchSize, runTime, ...)用于执行一轮指定操作的基准迭代。
三大压测场景:读落在哪里决定了场景
三个服务器配置的核心区别在于读请求落在哪里,并通过线程扫描获得离线吞吐量。其中分散-聚合 GET(--sg-get,默认开启)会把连续的 pending GET 合并成一次向量化 IO——这对设备支撑的场景至关重要。
场景 1:内存受限——数据全部在 RAM
默认服务器配置,数据集完全落在内存日志中,读请求永远不会触达设备:
dotnet $GS --port 6379 & dotnet $RB --op GET --dbsize 16777216 --valuelength 100 --runtime 0 # 加载 16 M × 100 B dotnet $RB -s --op GET --dbsize 16777216 --valuelength 100 -t 1,2,4,8,16,32 -b 1024场景 2:NVMe 存储受限——读请求真实命中磁盘
通过给存储分层(storage tiering)配一个极小的内存日志,让 100 M 数据集中的约 99.9% 落在 NVMe 上,每个 GET 都是一次随机设备取数。使用 100 M × 128 B 记录(--keylength 16 --valuelength 96,与 KV/Device 基准一致——128 B 记录在阵列的 512 B 扇区上读取)。参考主机为8×NVMe RAID-0(/raid,fio随机读上限 ≈4K 时 8.24 M IOPS/512 B 时 8.20 M——阵列是 IOPS 受限的,块大小几乎不影响);Garnet 端到端可维持约 7.3 M(约为 fio 的 89%)。
DATA=/raid/garnet; mkdir -p $DATA # 服务器固定到 NUMA 节点 0,客户端在节点 1 上驱动: numactl --cpunodebind=0 --membind=0 dotnet $GS --port 6379 --bind 127.0.0.1 \ --memory 16m --page 4m --segment 1g --index 8g --storage-tier --logdir $DATA \ --device-type Native --device-io-backend Libaio --logger-level Warning & numactl --cpunodebind=1 --membind=1 dotnet $RB --op MSET --dbsize 100000000 \ --keylength 16 --valuelength 96 --client LightClient --load-threads 32 -b 4096 --runtime 0 numactl --cpunodebind=1 --membind=1 dotnet $RB -s --op GET --dbsize 100000000 \ --keylength 16 --valuelength 96 --client LightClient -t 8,32,48,64 -b 1024 --runtime 12在相信 GET 数字之前务必核对加载是否完整。一个从未被写入的键会直接从内存应答而不触达设备,而一次未命中比一次磁盘读便宜约 30 倍,因此部分加载会静默虚高结果——未加载完成的存储在参考主机上会报告 >150 M ops/sec。确认MSET步骤在其[Total time]行报告约 1 亿次操作;仓库自带的生成脚本 nvme-raid0-matrix.sh 会强制这一校验(它以加载器报告的 op 数为准,要求不低于 dbsize 的 99%,因为对 100 M 键的存储做全索引扫描作为启动探针太慢)。
| backend | NUMA | t=8 | t=32 | t=48 | t=64 |
|---|---|---|---|---|---|
| Libaio | srv node-0 / cli node-1 | 1.82 M | 5.70 M | 6.97 M | 7.06 M |
| Libaio | no pin | 1.38 M | 5.09 M | 5.72 M | 5.57 M |
几个要点(均来自仓库 README 的实测结论):
--index 8g用于 100 M 键(默认 128 m 会因哈希链造成 3–4 倍降速)。- 峰值落在t=48–64区间:RESP 服务器的流水线客户端连接通过服务器自身的网络线程与完成线程驱动在途深度,因此吞吐会持续爬升,越过原始设备在 t=32 的峰值后才被排队成本追上。
- NUMA 固定在这里最重要(有状态服务器):把服务器固定到节点 0、客户端固定到节点 1,可使 t=48 从 5.72 M 提升到 6.97 M、t=64 从 5.57 M 提升到 7.06 M。
Libaio是 Linux 默认后端,无需 ring 调优。Uring现在会自动把 ring 数调整为min(2 × cores, 64)——与--device-completion-threads解耦——因此开箱即可与 libaio 竞争;超高并发时可用--device-io-contexts N显式设置 ring 数(应大于等于连接数)。- 容量旋钮在上述命令中保留默认值;可追加
--device-completion-threads 8 --device-throttle-limit 4096手工调优。--device-throttle-limit 4096适合本阵列;单盘/SATA 盘应降到 512/128。
场景 3:内存设备受限——读请求命中内存中的设备
与场景 2 相同的分层服务器,但使用无系统调用的LocalMemory设备:完整 RESP + pending 路径,磁盘延迟为零(软件上限;与 KV 和 Device 的 LocalMemory 运行对齐)。把场景 2 中的设备参数替换为:
... --device-type LocalMemory --device-completion-threads 4 --device-throttle-limit 512 ...参考数据(10 M × 100 B,t=16):-b 1024时约2.7 M ops/sec,-b 256时约3.7 M。
可复现的实测矩阵:8×NVMe SSD RAID-0
以下是场景 2完整 GET 吞吐矩阵,采用开箱即用的设备默认配置——只设置--storage-tier和--device-io-backend,--device-completion-threads、--device-throttle-limit、--device-io-contexts全部保持服务器默认,因此这代表运维人员不做任何设备调优即可获得的水平。每个单元取 3 次的中位数。
主机:2× Intel Xeon Platinum 8480CL(每插槽 56 核 × 2 线程,224 个逻辑 CPU,2 个 NUMA 节点),约 2 TB DDR5;8× Kioxia KCM6DRUL3T843.84 TB PCIe-Gen4 NVMe 组成 LinuxmdRAID-0(/dev/md1,512 KB chunk,ext4,约 28 TB);Ubuntu 24.04.4 LTS,内核 6.8.0-136,.NET 10.0.302;fs.aio-max-nr= 4194304。该阵列的fio随机读上限:4K 时 8.24 M IOPS、512 B 时 8.20 M IOPS(32 jobs × QD64,io_uring,O_DIRECT,8 个文件)——阵列在这些尺寸下是 IOPS 受限的,上限实际上与块大小无关。
负载:100 M × 128 B 记录(--keylength 16 --valuelength 96)分层落到阵列上(--memory 16m --page 4m --segment 1g --index 8g);100% 随机 GET;客户端-b 1024;每单元 12 秒。生成脚本会在测量前验证加载确实写入了约 1 亿个键,因此下面每个单元都是存储受限的,而非部分由内存未命中服务。
| backend | NUMA | t=8 | t=32 | t=48 | t=64 |
|---|---|---|---|---|---|
| Libaio | srv node-0 / cli node-1 | 1.82 M | 5.70 M | 6.97 M | 7.06 M |
| Libaio | no pin | 1.38 M | 5.09 M | 5.72 M | 5.57 M |
| Uring | srv node-0 / cli node-1 | 1.82 M | 6.13 M | 7.32 M | 6.89 M |
| Uring | no pin | 1.48 M | 4.60 M | 5.55 M | 6.29 M |
结论要点:
- 峰值约 7.3 M ops/sec(uring、固定、t=48)——约为同形态随机读
fio上限的89%(128 B 记录经阵列的 512 B 扇区取数),端到端穿过 RESP 协议和 Tsavorite pending-read 路径(而非原始设备 IO)驱动。与 fio 的剩余差距来自 RESP + pending-read 路径:原始设备层在该阵列上可达约 8.4 M。 - 两个后端在不同线程数达到峰值。Uring 在 t=48 见顶、t=64 回落——额外的客户端并发带来的排队成本超过了在途深度的收益;libaio 在 t=64 仍在爬升。应扫描 t=48–64 区间,而不是假定单一最优值。
- 默认配置即可达到调优后的峰值。Uring 的智能 ring 数默认值(
min(2 × cores, 64)个 ring,与 4 个完成线程解耦)无需任何参数即可把 ring 匹配到硬件;libaio 无需 ring 调优。显式调优(--device-completion-threads 8 --device-throttle-limit 4096,uring 加--device-io-contexts 96)在本主机上每个单元只移动几个百分点。 - NUMA 固定是这台双插槽机器上最大的单一因素(有状态服务器):例如 uring 在 t=48 从 5.55 M 升到 7.32 M——把服务器固定到节点 0、客户端固定到节点 1。单插槽主机上固定/不固定两行会趋同。
- 两个后端在各自拿到所需 ring 配置后都达到约 7 M 的平台——uring 在 t=32–48 领先 5–8%,libaio 在 t=64 领先约 2%——因此任选一个后端并调优线程数即可。
用生成脚本一键复现
仓库提供了自动化生成脚本(需 Release 构建的GarnetServer+Resp.benchmark、numactl,以及 NVMe / O_DIRECT 挂载点):
DATA=/mnt/nvme/garnet benchmark/Resp.benchmark/scripts/nvme-raid0-matrix.sh该脚本扫描 两个后端 × 固定/不固定 × 线程数,对每个单元取PASSES(默认 3)次的中位数,并打印上文的 Markdown 表格。可通过环境变量覆盖DATA、THREADS、PASSES、RUNTIME,或设置CT/THROTTLE/URING_IOCTX来运行显式调优配置而非默认配置。脚本内置了加载完整性校验(加载 op 数低于 dbsize 的 99% 即中止)和按真实GarnetServer.dllPID 停止服务器的逻辑(停止dotnet启动器会遗留存活的运行时子进程,泄漏的自旋服务器会造成巨大的方差)。
离线变体:MGET、InProc、SERedis、Zipf 与集群
dotnet $RB --op MGET --dbsize 16777216 --valuelength 8 -t 16 -b 512 # MGET(分散-聚合) dotnet $RB --op GET --dbsize 1000000 -v 100 -t 16 -b 1024 --client InProc # 仅服务器 CPU,无 TCP dotnet $RB --op GET --dbsize 1000000 -v 100 -t 16 -b 256 --client SERedis # 与 Redis 同类对比 dotnet $RB --op GET --dbsize 16777216 -v 100 -t 16 -b 1024 --zipf # 偏斜键(θ=0.99) dotnet $RB --cluster-bench --op GET --dbsize 16777216 -t 16 -b 1024 --zipf # 每个分片内部偏斜在线模式(延迟测量)
# 单客户端 GET 延迟(最干净的读数) dotnet $RB --online --op-workload GET --op-percent 100 --dbsize 1000000 -t 1 -b 1 --runtime 30 # 50/50 GET/SET 尾延迟,16 个连接 dotnet $RB --online --op-workload GET,SET --op-percent 50,50 --dbsize 1000000 -t 16 --runtime 60 --client GarnetClientSession # 固定施加负载:8 连接 × 64 在途 dotnet $RB --online --op-workload GET --op-percent 100 --dbsize 1000000 -t 8 --itp 64 --client GarnetClientSession--runtime -1表示运行到被中断为止;在线模式下0无效。在线模式也支持--client SERedis(如 ZADD/ZCARD 混合:--op-workload ZADD,ZCARD --op-percent 50,50 --keylength 8 --client SERedis)。在线模式会强制-b 1,并发度用--itp表达;--pool参数在LightClient下不受支持,需改用 GarnetClientSession / GarnetClient / SERedis。
在线模式方法论(在相信数字之前必读)
- 纯加载=
--op GET --runtime 0(只播种键空间,不进入运行阶段),之后读阶段加-s。 - 验证数据确实在磁盘上:
redis-cli INFO store——Log.TailAddress应与数据集大小匹配,且Log.HeadAddress ≈ TailAddress(数据已从小的内存区域驱逐到设备)。 - 确认读确实命中设备。在大内存主机上整个数据集会落在页缓存里;存储设备以
O_DIRECT打开,读会绕过页缓存。读阶段中iostat -x 1应显示 NVMe 的r/s ≈ ops/sec、aqu-sz ≈ --device-throttle-limit。在共享机器上应读取每进程的/proc/<GarnetServer pid>/io的read_bytes——它排除其他租户的 IO。 - 多跑几轮读阶段并取稳态——第一轮是热身。
- 公平 A/B:每个变体分别构建/加载/运行。为对抗 CPU 时钟漂移,可在不同端口同时跑两个服务器并交错运行。停止服务器要按其真实
GarnetServer.dllPID——停止dotnet启动器会遗留存活的运行时子进程,泄漏的自旋服务器会造成巨大方差。
输出解读
- 离线模式:
[Total time]: <ms> for <ops>和[Throughput]: <ops/sec>(= ops_done × batch / runtime)。 - 在线模式:每 2 秒打印
min; 5th; median; avg; 95th; 99th; 99.9th; total_ops; iter_tops; tpt(Kops/s)(单位为 µs;每线程 HdrHistogram)。
常见问题排查
| 症状 | 解决办法 |
|---|---|
Skipload not supported with --online | 去掉-s或改用离线模式 |
在线模式出现-b N>1警告 | 工具会强制-b 1;并发请用--itp |
--pool与LightClient组合不受支持 | 改用 GarnetClientSession / GarnetClient / SERedis |
| 磁盘受限吞吐远低于 KV.benchmark | 服务器端瓶颈——用dotnet-trace做性能剖析 |
--dbsize % loadThreads != 0 | 把--dbsize取整为加载线程数的倍数 |
另外,若想知道命中率,可用任意客户端执行INFO stats查看garnet_hit_rate指标(理想情况下应接近 100),前提是服务器启用了指标采样:--latency-monitor --metrics-sampling-freq 5。
BDN.benchmark:精确可复现的微基准
BDN.benchmark命令行工具基于 BenchmarkDotNet:它先解析 Garnet 自定义参数(--frameworks/--fw过滤 .NET 版本,--opparams/--op过滤 ACL/AOF/AAD 参数组合),再把剩余参数透传给 BenchmarkDotNet。默认BaseConfig使用工作台 GC(非并发),以便 MemoryDiagnoser 确定性地测量分配(Server GC 下GC.GetTotalAllocatedBytes会间歇性高估);对 net8.0/net10.0 分别建立 Job 并显式禁用动态 PGO(DOTNET_TieredPGO=0),保证结果可复现。
用法
列出所有可用基准(--list flat平铺或--list tree树状):
dotnet run -c Release -f net8.0 --list flat运行特定基准可用--filter。例如,以默认配置运行所有 RESP 协议写类基准(默认会在所有 .NET 目标运行时上运行,禁用动态 PGO):
dotnet run -c Release -f net8.0 --filter *RespIntegerWriteBenchmarks*--filter支持通配符,可按类名或方法名筛选。Garnet 自定义参数示例:dotnet run -f net10.0 -c Release -- --fw net10.0 --op none -f *RawStringOperations*(其中--op none表示只跑无 ACL/AOF/AAD 参数组合的基准)。更多 BenchmarkDotNet 命令行选项见其控制台参数文档。
编写微基准
微基准类按功能域组织在 benchmark/BDN.benchmark 下,例如Operations/RawStringOperations.cs(SET/SETEX/SETNX/GET/INCR/DECR 等字符串命令)、Operations/HashObjectOperations.cs、Operations/SortedSetOperations.cs、Operations/JsonOperations.cs、Operations/ScriptOperations.cs、Cluster/、Lua/、Auth/等。所有操作基准继承 OperationsBase.cs,它利用EmbeddedRespServer在进程内创建真实服务器与RespServerSession,通过 GlobalSetup 一次性完成配置(支持按参数开关 ACL 认证、AOF 或 AAD 认证,从而测量不同运行模式下的命令成本),并以批大小 100 执行命令——据此可便捷换算吞吐:5 µs ≈ 20 Mops/sec、10 µs ≈ 10 Mops/sec。例如BasicOperations.InlinePing直接以"PING\r\n"原始字节衡量内联命令解析成本。编写新微基准时应遵循 BenchmarkDotNet 的 Microbenchmark Design Guidelines(基准方法应幂等、避免 JIT 消除、使用全局 Setup 预热等)。
Device.benchmark 与 KV.benchmark:下两层的纵深参考
Resp.benchmark的三个场景与下层基准一一对应,理解它们有助于定位瓶颈到底在哪一层。
Device.benchmark:原始 IDevice IOPS
位于 libs/storage/Tsavorite/cs/benchmark/Device.benchmark,用扇区对齐的图案填充 backing 文件,然后 N 个 worker 各在随机偏移上发起device.ReadAsync并在完成回调中回收缓冲区。吞吐只统计成功完成。其 NVMe 存储受限场景使用与 KV/RESP 兼容的配置:--sector-size 512(阵列的逻辑块大小)与--file-size 12801015808(12.8 GB = 100 M × 128 B)。在 8×NVMe RAID-0 上,libaio(8 个 io_context)与 uring(智能默认 64 个 ring)都能达到 fio 上限——libaio 8.23 M / uring 8.45 M——而把 uring 的 ring 显式欠配到 8(--device-io-contexts 8)会暴露每 ring SpinLock 上限,跌到约 2.9 M。这解释了 RESP 层为什么会建议uring使用min(2 × cores, 64)的智能默认。核心参数:--device-throttle-limit(用户侧在途上限,快阵列用 4096、单 NVMe 用 512、LocalMemory 用大值)、--device-completion-threads(后台 drainer 数,快阵列 8)、--device-io-contexts(内核 io_context/io_uring ring 数,与 drainer 解耦)。LocalMemory 场景中当--device-completion-threads == --threads(每提交者一个 SPSC ring)时测得提交/完成路径上限约78 MIOps/s(t=32)——这是 KV/RESP LocalMemory 运行的上界。其完成模型为 block-on-signal(非忙等),在云级高延迟设备(Azure/EBS 级,约 0.5–2 ms)上是稳态路径,吞吐按延迟×并发受限。
KV.benchmark:Tsavorite 引擎吞吐
位于 libs/storage/Tsavorite/cs/benchmark/KV.benchmark,在 8 字节键 + 定长值数据集上测量 load 与 run(RUMD)吞吐,通过安全的BasicContext路径运行,刻意做到零每操作分配、NUMA 固定 worker、无伪共享的记分板与中央 tick 计时。同样有三个场景:--device null的纯引擎上限、--device native --log-memory 16m的 NVMe 存储受限(libaio 在 t=64 达约7.7 M,为 fio 的约 94%)、--device localmemory --device-inline-completion的内存设备受限(约19.6 M,t=32)。在磁盘受限场景,负载必须用 100 M+ 键——更小的数据集只触及少量 NAND die 会低估 IOPS。
结语:一套贯穿四层的压测方法论
从Resp.benchmark的三大场景出发,Garnet 提供了自上而下贯穿"完整 RESP 服务器 → Tsavorite 引擎 → 原始 IDevice → fio"的四层压测栈:先在 RESP 层用--dbsize/--valuelength/--keylength对齐 128 B 记录、用-b 1024控制流水线深度、用-s --runtime 0完成纯加载,再借助redis-cli INFO store与iostat -x 1验证读请求真正落在设备上;若吞吐低于预期,用 Device/KV 层判断瓶颈属于设备后端(ring 配置、throttle 上限)还是服务器侧(NUMA、完成线程、流水线深度)。对单条命令的成本分析则落到BDN.benchmark的嵌入服务器微基准。这套方法论既给出了开箱即用的复现脚本(nvme-raid0-matrix.sh),也给出了手工验证与调优的完整手段,是理解与优化 Garnet 性能的可靠起点。
【免费下载链接】garnetGarnet is a remote cache-store from Microsoft Research that offers strong performance (throughput and latency), scalability, storage, recovery, cluster sharding, key migration, and replication features. Garnet can work with existing Redis clients.项目地址: https://gitcode.com/GitHub_Trending/garnet4/garnet
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考