Show HN:Keybench 是一款可脚本化且可扩展的键值存储性能测试工具
原标题:Show HN: Keybench – Scriptable, extensible performance tool for key value stores
速览
Keybench 是一款专为键值存储设计的性能基准测试工具。它支持脚本化操作和高度扩展,旨在帮助开发者更灵活、高效地评估存储系统的性能表现。
AI 深度解读
Show HN: Keybench – 面向键值存储的可脚本化、可扩展性能测试工具
背景
在数据库和键值存储(Key-Value Store, KVS)领域的开发与选型过程中,性能基准测试(Benchmarking)是至关重要的一环。然而,现有的许多基准测试工具往往存在“黑盒”问题:它们内置了固定的工作负载(Workload),用户难以根据特定的业务场景(如电商购物车结算、特定比例的读写混合等)进行灵活定制。此外,不同存储引擎之间的对比往往因为测试框架本身的开销或实现差异而引入偏差,导致测试结果难以真实反映存储引擎本身的性能。
在此背景下,keybench 作为一个开源项目出现在 Hacker News 上。它是一个专为排序键值存储设计的、高度可脚本化且可扩展的性能测试工具。其核心理念是“让脚本跨引擎无缝运行”,从而确保对比测试衡量的是存储引擎本身的差异,而非测试框架(Harness)的差异。
核心内容
keybench 的设计哲学在于解耦工作负载定义与底层存储实现,通过 Lua 脚本驱动测试,并提供细粒度的性能指标报告。以下是其核心机制的详细解读:
1. 测量指标体系
keybench 不仅报告传统的吞吐量,还引入了更细致的维度来区分业务操作与底层 I/O 操作:
- wu/s (Workload Units per second):每秒工作负载单元数。一个“单元”对应脚本中
run()函数的一次调用。这代表了业务视角的操作速率(例如一次“查看购物车”或一次“结账”),无论该操作涉及多少次底层键值触碰。 - ops/s (Primitive Operations per second):每秒原始操作数。一个原始操作指单次
put、get、del、range或scan调用,或者是mget/mput/mdel中处理的一个键。range或scan即使返回多行,也只计为一次操作。这代表了底层键值触碰的速率。- 当每个工作负载单元仅包含一个原始操作时,
wu/s和ops/s相等。 - 当工作负载单元包含多个原始操作(如批量写入 B 个键,或先扫描后删除的结账流程)时,报告会分别打印这两行。此时
ops/sec恰好是wu/sec的 B 倍。这种差异清晰地展示了批量处理带来的固定调用成本分摊效应。
- 当每个工作负载单元仅包含一个原始操作时,
- 延迟分布 (Latency Distribution):
keybench拒绝使用平均值,而是记录每个操作类型的延迟分布直方图。针对put、get、del、range、mget、mput和mdel分别建立直方图(scan计入range直方图)。报告提供 p50、p99、p99.9 以及最大值。
2. 架构设计:五部分可替换模块
keybench 由五个可替换的部分组成,实现了高度的模块化:
- 引擎并发管理 (Engine Concurrency):
keybench负责生成工作线程,将操作预算分配给它们,并等待完成。- 每个工作线程拥有独立的 Lua 状态和随机种子。
- 关键设计:测试框架在调用引擎时从不持有锁。这意味着序列化引擎会如实报告为序列化,并行引擎如实报告为并行。Lua 脚本保持单线程逻辑,无需处理锁机制。
- 工作负载定义 (Workload):
- 工作负载是一个 Lua 表,包含名称、可选的
load函数和必需的run函数。 load函数用于在计时前预热存储引擎。它在所有工作线程上并行运行,每个线程填充自己的数据分片(Shard),从而实现大数据集的高速并行加载。run函数是被计时的基本单元。框架调用它直到耗尽操作预算或时间预算。- 两个函数接收一个
ctx表,包含用户数、商品数、操作数、种子、批次大小、线程索引、总线程数及当前迭代次数。
- 工作负载是一个 Lua 表,包含名称、可选的
- 存储接口 (Store Interface):
- 脚本通过全局
kv表调用存储引擎。无论选择哪个后端,统一的八个动词(verbs)接口保持不变。 keybench负责计时、将样本填入对应的直方图以及统计原始操作数。
- 脚本通过全局
- 后端插件 (Backend Plugin):
- 后端采用自注册插件机制。每个引擎位于
backends/目录下,拥有独立的构建片段,并在main运行前注册自身。 - 通过命令行标志选择引擎名称,或选择多个引擎进行对比。添加新引擎只需新增目录和虚表(vtable),无需修改核心代码。
- 后端采用自注册插件机制。每个引擎位于
- 报告器 (Reporter):
- 报告器作为数据接收端(Sink)。运行期间,框架广播元数据、探针数据、聚合点和实时样本给所有启用的报告器。
- 支持多种报告器同时运行:控制台报告器(人类可读)、TSV 报告器(机器可读行)、时间线报告器(每行一个实时样本)。
3. 构建与配置
keybench 自带 Lua 依赖,默认构建仅需 C 编译器和 pthreads。
- 默认构建:
make生成包含内存跳表(skiplist)引擎的./keybench。跳表无需外部库,作为参考引擎。 - 持久化引擎支持:
make ROCKSDB=1:添加 RocksDB 引擎。make TIDESDB=1:添加 TidesDB 引擎。- 支持同时编译多个引擎:
make BACKENDS="skiplist rocksdb tidesdb"。
- 内存分配器:
- 默认使用系统分配器。
- 如果系统安装的 RocksDB 或 TidesDB 是使用
jemalloc构建的,建议链接相同的分配器以避免跨sweep点重新打开数据库时的崩溃:ALLOC=jemalloc。 - 支持
tcmalloc或其他自定义路径:ALLOC_LIBS="..."。 - 支持覆盖引擎库和头文件路径,适配自定义安装。
- 系统 Lua:使用
make LUA_SYS=1链接系统 Lua 而非内置版本。
4. 运行与监控
- 基本运行:
./keybench workloads/mixed.lua。默认在单线程上对跳表引擎执行 200,000 个工作负载单元,并打印控制台报告。 - 报告结构:
[probe: system]:记录主机 OS、CPU、内存、文件系统空间及磁盘类型。[probe: build]:记录编译器版本、C 标准及内存分配器。- 操作表:列出每种操作的计数、p50/p99/p99.9/max 延迟。
- 命中率:
get操作中找到键的比例。 - 吞吐量:显示
wu/sec,若与ops/sec不同则同时显示。
- 实时进度 (Live Progress):
- 测试期间,
keybench每秒向stderr流式传输一行状态信息,防止长测试看起来像卡死。 - 输出格式为日志滚动形式,而非覆盖单行。
- 仅当
stderr为终端时才显示状态,避免污染管道输出或日志文件。 - 时间受限测试显示:已用时间/预算时间,当前速率。
- 计数受限测试显示:已用时间,当前速率,完成百分比及具体计数。
- 测试期间,
5. 参数控制与网格测试
- 边界控制:
--ops N:总工作
查看原文 →github.com
