NebulaGraph 存储性能压测工具 storage-perf 实战指南:压测参数、方法选择与数据完整性校验
【免费下载链接】nebulaA distributed, fast open-source graph database featuring horizontal scalability and high availability项目地址: https://gitcode.com/gh_mirrors/nebul/nebula
storage-perf是 NebulaGraph 仓库中用于直接压测存储服务(storage service)的基准测试工具,它绕过 Graph 层、以 RPC 方式向 Storage 服务灌入读写负载,帮助开发者在部署真实业务前量化getNeighbors、addVertices、addEdges等核心接口的吞吐与延迟。本文以 src/tools/storage-perf/README.md 为骨架,结合 StoragePerfTool.cpp 与 StorageIntegrityTool.cpp 的源码实现,完整讲解工具的全部配置参数、压测方法与输出指标含义,并介绍配套的数据完整性校验工具。读完本文,你将能够独立搭建压测环境、正确配置参数、读懂压测日志,并用环形链表方案验证存储数据在读写后的完整性。
工具概览:一个入口、两类能力
按 src/tools/storage-perf/README.md 的说明,_build/storage_perf是用于测试存储服务的性能工具。该目录下包含两个相互独立的可执行目标:
- storage_perf:性能压测工具,源码为 StoragePerfTool.cpp,支持多种 RPC 方法的混合/单一压测;
- storage_integrity:数据完整性校验工具,源码为 StorageIntegrityTool.cpp,其设计基于 HBase 的
IntegrationTestBigLinkedList思路(README 中明确说明 "Integration test is based onIntegrationTestBigLinkedListof HBase")。
两个工具的共同点是:都直接通过 StorageClient 与 meta 服务、storage 服务通信,不经过 Graph 查询层,因此测到的是存储引擎与 RPC 链路的真实能力。
需要说明的是,从当前仓库源码看,src/tools/storage-perf/CMakeLists.txt 中两个可执行目标的nebula_add_executable与install规则均处于注释状态,且 src/tools/CMakeLists.txt 仅在未启用 standalone 版本(NOT ENABLE_STANDALONE_VERSION)时才添加storage-perf子目录。因此在实际使用前,需要根据你的构建环境按需启用该目标的编译,README 中_build/storage_perf即为构建产物路径。
测试前置条件:空间、Tag 与 Edge
README 明确指出:测试前需要先创建图空间(graph space)、Tag 和 Edge 类型。工具默认使用的名字分别为:
- 图空间:
test - Tag:
test_tag - Edge:
test_edge
这三个名字分别对应源码中的默认参数FLAGS_space_name、FLAGS_tag_name、FLAGS_edge_name(StoragePerfTool.cpp)。
从源码实现看,工具的初始化流程会进一步印证这一前提(StoragePerfTool.cpp):
- 解析
meta_server_addrs得到 meta 服务地址列表(通过NetworkUtils::toHosts),无法解析则直接退出; - 创建
folly::IOThreadPoolExecutor(线程数由io_threads控制)与MetaClient(skipConfig_ = true),并调用waitForMetadReady()等待 meta 服务就绪; - 通过
getSpaceIdByNameFromCache按名字解析 SpaceID——图空间必须已存在,否则报Get SpaceID Failed并退出; - 解析 Tag:若
test_tag不存在,工具会自动创建一个仅含一个 STRING 类型属性col_1的 Tag schema,然后等待heartbeat_interval_secs + 1秒让 schema 同步生效;若已存在则直接使用,并读取其全部属性名用于构造请求; - Edge 的处理逻辑与 Tag 完全对称:不存在则自动创建
col_1属性,存在则复用。
这意味着:图空间需要你预先通过 Graph 客户端(如nebula-console)创建,而 Tag/Edge 可以由工具自动补齐,但为保证压测数据的可预期性,建议仍按 README 建议提前创建好三者。
配置参数全解析
README 给出了完整的参数参考表,下表逐项继承并补充源码中的实际定义(源码位于 StoragePerfTool.cpp):
| 属性名 | README 默认值 | 源码默认值 | 说明 |
|---|---|---|---|
threads | 2 | 1 | 压测总线程数,每个线程独立循环发送请求 |
qps | 1000 | 1000 | 压测工具的总目标 QPS |
totalReqs | 10000 | 10000 | 压测期间的总请求数,达到后停止 |
io_threads | 10 | 10 | 客户端 IO 线程数(folly IOThreadPoolExecutor 线程池大小) |
method | "getNeighbors" | "getNeighbors" | 被测方法:getNeighbors、addVertices、addEdges、getVertices、getEdges |
meta_server_addrs | "" | "" | meta 服务地址,如127.0.0.1:9559 |
min_vertex_id | 1 | 1 | 最小的顶点 ID(工具内部会转换为字符串) |
max_vertex_id | 10000 | 10000 | 最大的顶点 ID(工具内部会转换为字符串) |
size | 1000 | —(见property_size) | 每个请求的数据量 |
space_name | "test" | "test" | 指定图空间名 |
tag_name | "test_tag" | "test_tag" | 指定 Tag 名(要求其属性全部为字符串类型) |
edge_name | "test_edge" | "test_edge" | 指定 Edge 名(要求其属性全部为字符串类型) |
random_message | false | true | 是否向存储服务写入随机消息 |
源码中 README 未列出的补充参数
对照源码可以发现,README 参数表之外还有三个实际生效的 gflags(这属于 README 与源码的差异,使用时以源码为准):
| 属性名 | 默认值 | 说明 |
|---|---|---|
property_size | 1000 | 单个属性的字符串长度(字节),即 README 中size的实际实现载体 |
concurrency | 50 | 单次批量发出的并发请求数,与令牌桶联动 |
batch_num | 1 | 每个请求批量包含的顶点/边数量 |
此外还有两个值得注意的 README 与源码默认值差异:threads(README 写 2,源码默认 1)与random_message(README 写 false,源码默认 true)。压测前应显式指定这些参数,不要依赖默认值。
参数的作用机制
min_vertex_id/max_vertex_id:决定随机顶点 ID 的取值区间。randomVertices()用folly::Random::rand32(max - min) + min生成随机 ID(StoragePerfTool.cpp),因此区间大小直接影响数据分布范围。random_message+property_size:genData()在random_message=true时为每个属性生成property_size长度的随机字符串(字符集为数字 + 大小写字母,见 StoragePerfTool.cpp);为 false 时属性值为空字符串。写入数据的真实负载大小 = 属性个数 × 属性长度。meta_server_addrs:格式与 conf/nebula-metad.conf.default 中的--meta_server_addrs=127.0.0.1:9559保持一致,多个地址用逗号分隔。
支持的压测方法与其实现细节
method参数支持五种方法(README 列举了四种,源码 StoragePerfTool.cpp 实际支持五种):
| method 取值 | 底层 StorageClient 调用 | 实现要点 |
|---|---|---|
getNeighbors | getNeighbors(StorageClient.h) | 查询随机顶点的邻居,边方向固定为EdgeDirection::BOTH,携带顶点属性与边属性请求 |
addVertices | addVertices(StorageClient.h) | 以ifNotExists=true, ignoreExistedIndex=false写入批量顶点,顶点 ID 从min_vertex_id起递增 |
addEdges | addEdges(StorageClient.h) | 批量写入边,每条边为vintId -> vintId+1、rank 为 0 |
getVertices | getProps(StorageClient.h) | 以kVid作为输入列构造 DataSet,返回随机顶点的 Tag 属性 |
getEdges | getProps | 以kSrc/kType/kRank/kDst四列构造 DataSet,返回随机边的属性 |
其中getVertices/getEdges在源码中通过统一的getProps接口实现,只是传入的VertexProp/EdgeProp不同。压测数据生成上,genVertices()与genEdges()都支持batch_num批量放大单请求负载(StoragePerfTool.cpp),边数据始终指向相邻递增的顶点,保证数据在拓扑上的自洽性。
压测执行流程与结果输出解读
运行模型
Perf::run()的主流程为(StoragePerfTool.cpp):初始化客户端 → 拉起threads个线程 → 每个线程在runInternal()中循环执行,直到全局finishedRequests_达到totalReqs。
runInternal()的循环体包含两个关键机制:
- 令牌桶限流:每次通过
tokenBucket_.consumeOrDrain(FLAGS_concurrency, FLAGS_qps, FLAGS_concurrency)从 folly 的DynamicTokenBucket申请令牌(StoragePerfTool.cpp),即单轮最多发concurrency个请求、总速率不超过qps; - 进度与指标上报:每处理 2000 个请求打印一次日志(
PLOG_EVERY_N(INFO, 2000)),随后usleep(500)微休眠,避免忙循环打满 CPU。
日志输出解读
压测过程中会周期性输出类似下面的进度日志:
Progress 42%, qps=1032, latency(us) median = 342, p90 = 891, p99 = 2134这些指标来自两个 folly 直方图对象latencies_与qps_(StoragePerfTool.cpp):
Progress:已完成请求数占totalReqs的百分比;qps:当前吞吐(qps_.rate(0));latency(us) median / p90 / p99:最近一个时间窗口内请求延迟(微秒)的 50 分位、90 分位与 99 分位估计值。
每个请求的延迟通过time::WallClock::fastNowInMicroSec()在发出前与回调返回后各取一次时间戳相减得到(见各*Task()实现)。压测全部结束后还会输出总耗时与总请求数:
Total time cost 12345ms, total requests 10000注意:getNeighbors、addEdges等任务中,若请求失败会在回调里打印Request failed及失败分片信息(addVerticesTask会打印具体的 partition 与错误码),这些信息同样是压测结论的重要输入。
Storage Integrity Tool:环形链表式数据完整性校验
README 的最后一段说明:集成测试基于 HBase 的IntegrationTestBigLinkedList。该思路在 StorageIntegrityTool.cpp 中有完整的源码实现,其核心思想是:构造一个环形链表状的数据矩阵,然后沿链表遍历,验证每条数据都能正确读写。
参数与默认值
| 属性名 | 默认值 | 说明 |
|---|---|---|
meta_server_addrs | "" | meta 服务地址 |
io_threads | 10 | 客户端 IO 线程数 |
space_name | "test_space" | 目标图空间名(注意与压测工具的默认test不同) |
first_key | "1" | 链表的起始 key(最小 key) |
width | 100 | 矩阵宽度(每行的节点数) |
height | 1000 | 矩阵高度(行数) |
校验原理
IntegrityTest类(StorageIntegrityTool.cpp)把数据组织成一个width × height的矩阵:每个 key 的 value 是"下一个节点"的 key,所有 key-value 构成一条首尾相接的大链表。其数据排布可用源码注释中的示意图理解——首行、前驱行、当前行逐行串接,最后一行经过旋转后与首行闭合,从而形成一条可从任意节点出发、走width * height步回到起点的环形链表。
具体流程为:
prepareData():先为第一行生成width个 key(从first_key开始递增);然后逐行调用insertRow()写入中间各行(每个 key 指向上一行对应位置的 key);最后将末行旋转一位再插入首行,完成闭环(StorageIntegrityTool.cpp);- 写入通过
client_->put(spaceId_, keyValues)批量完成,每 10000 个 key-value 打印一次进度; validate():从first_key出发,循环width * height次,每次用client_->get(spaceId_, {nextId})取回下一个 key(StorageIntegrityTool.cpp),若中途取不到值或最终没有回到起点,则校验失败并返回非零退出码。
因此,该工具不仅能验证"写入后可读回",还能验证整条数据链的连续性——任何一条数据丢失或错乱都会导致遍历中断或终点不闭合,是检测存储层数据丢失、损坏等问题的有效手段。初始化时若width * height超过int32上限会直接报错退出。
源码阅读指引
如果希望深入理解这两个工具的实现,建议按以下路径阅读:
- 压测主逻辑:StoragePerfTool.cpp(
Perf::run/runInternal/ 五个*Task方法); - 完整性校验:StorageIntegrityTool.cpp(
prepareData/validate); - 底层 RPC 客户端接口:src/clients/storage/StorageClient.h(
CommonRequestParam结构与getNeighbors/addVertices/addEdges/getProps声明); - 构建集成:src/tools/CMakeLists.txt 与 src/tools/storage-perf/CMakeLists.txt;
- 服务端默认端口与 meta 地址配置:conf/nebula-metad.conf.default。
结合这些源码,你可以在压测前准确预估每个参数对负载模型的影响(例如concurrency决定瞬时并发、qps决定稳态速率、property_size决定单请求字节数),从而设计出贴合真实业务场景的压测方案。
【免费下载链接】nebulaA distributed, fast open-source graph database featuring horizontal scalability and high availability项目地址: https://gitcode.com/gh_mirrors/nebul/nebula
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考