- 数据库
- 后端
【免费下载链接】convex-backend
The open-source reactive database for app developers
导读
本文基于 self-hosted/advanced/benchmarking.md 及仓库中开源的压测工具 LoadGenerator(位于 crates/load_generator)编写,完整讲解如何对自托管(self-hosted)的 Convex Backend 实例执行负载测试与性能基准测试。读完本文,你将掌握:LoadGenerator 的整体架构与工作流程、面向自托管实例的两步压测流程(部署 ScenarioRunner 函数 + 运行 LoadGenerator)、各类内置 workload 配置的字段含义,以及如何编写自定义压测场景与自定义 Convex 函数,从而量化评估自托管实例的函数延迟、吞吐量与整体稳定性。
注意:LoadGenerator 是专用于 Convex 生态的压测工具,若你的目标是更通用的 HTTP 层压测,可结合本文思路自行扩展;本文聚焦仓库中已验证的官方流程。
一、LoadGenerator 是什么
LoadGenerator 是 Convex 开源仓库中自带的压测与基准测试工具,定位是"评估 Convex 在不同负载模式下的性能表现"。根据 crates/load_generator/README.md,它可以测量三方面核心指标:
- 函数延迟(Function latency):query / mutation / action / HTTP action 等请求的响应时延;
- 吞吐量(Throughput):在给定速率(每秒请求数)或并发线程数下,后端能承受的处理能力;
- 整体稳定性(Overall system stability):长时间高负载下,系统是否出现错误率飙升、内存泄漏或功能异常。
工作流程
LoadGenerator 的完整工作链路(编号遵循原文档)如下:
- Provisioning(实例准备):自动创建(provision)一个 Convex 实例,或直接指向一个已存在的实例;
- 下发场景(Sending scenarios):将预定义或自定义的压测场景发送给 ScenarioRunner;
- 收集指标(Collecting metrics):从已执行的场景中收集性能指标(函数执行耗时、事件等);
- 生成统计报告(Generating reports):生成包含延迟指标(p50 / p95 / p99 等)的详细统计报告;
- 可选:上报监控系统:将指标发送到生产监控系统(如 Datadog),实现长期观测。
架构图
原文档给出了如下的组件协作关系,LoadGenerator 是中枢:它向 ScenarioRunner 发送压测场景,ScenarioRunner 再以 query / mutation 的形式访问 Backend(被测的 Convex 后端);反向地,ScenarioRunner 把执行产生的事件(Events)回传给 LoadGenerator,由其生成 Stats Report;同时 LoadGenerator 的指标可被 Metrics Collector(如 Datadog、Prometheus)采集。
┌──────────┐ │ Stats │ │ Report │ ┌────────────────┐ ┌───────────────────┐ ┌───────────────┐ └──────────┘ │ │ │ │ queries, │ │ ▲ │ │ Scenarios │ │ mutations │ │ └─────────┬───│ LoadGenerator │──────────▶│ ScenarioRunner │──────────▶│ Backend │ ┌──────────────┐ │ │ │◀──────────│ │◀──────────│ │ │ │ │ │ │ Events │ │ │ │ │ Metrics │ │ └────────────────┘ └───────────────────┘ └───────────────┘ │ Collector │◀─┘ │(e.g. Datadog)│ │ │ └──────────────┘从源码实现看,这一架构在 crates/load_generator/src/main.rs 中有明确对应:run_workload函数会以node dist/scenario-runner.js --deployment-url <backend_url> --admin-key <admin_key> --load-generator-port <port> --scenarios <json>的方式直接 spawn 一个 ScenarioRunner 子进程(注意源码注释说明:必须直接 spawnnode,而不能用npm start,否则子进程无法从父进程被可靠 kill,参见 npm issue #4603);LoadGenerator 自身则通过 axum 启动一个 HTTP/WebSocket 服务(/sync路由),接收 ScenarioRunner 推送回来的事件,事件经EventProcessor汇总到Stats,在压测时长结束后生成统计报告。
二、快速开始:运行 LoadGenerator
查看帮助
在仓库根目录(convex目录)下执行:
cargo run -p load_generator --bin load-generator -- --help即可看到全部命令行参数说明。若需要 tracing 日志,在命令前加上环境变量:
RUST_LOG=info cargo run -p load_generator --bin load-generator -- --help预配置的工作负载
仓库的Justfile(crates/load_generator/Justfile)预置了多套运行方式,核心差异在 provisioner(实例供给方):
self-hosted:面向自托管后端(本文重点,见下一节);dev/dev-quick/dev-conductor:面向本地开发环境,绕过 Big Brain 直接连本地进程;dev-bb:连本地 Big Brain;prod:连生产 Big Brain,需要设置CONVEX_OVERRIDE_ACCESS_TOKEN环境变量(来自 1Password 的密钥)。
所有预置 workload 命令都带有--stats-report --once --duration 60(dev-quick为 10 秒),即运行 60 秒、打印统计报告后退出一次。例如:
just dev light等价于(在仓库根目录):
cargo run --release -p load_generator --bin load-generator \ -- crates/load_generator/workloads/light.json --duration 60 \ --provisioner open-source-release --stats-report --once主要命令行参数
结合 crates/load_generator/src/main.rs 中Config结构体的 clap 定义,常用参数如下:
| 参数 | 说明 | 默认值 |
|---|---|---|
--workload <path>(位置参数) | workload 配置文件路径,运行时解析为Workload结构 | 必填 |
--duration <secs> | LoadGenerator 运行秒数,超时后关闭连接、结束场景 | 必填 |
--stats-report | 结束后打印统计报告(含延迟分位数) | 关闭 |
--once | 只运行一轮 workload 后退出;不加则循环运行 | 关闭 |
--interface/--port | HTTP/WebSocket 服务绑定的网卡与端口 | 0.0.0.0/8010 |
--metrics-addr | Prometheus 指标导出地址(如0.0.0.0:9100) | 无 |
--provisioner <name> | 实例供给方(open-source-release等),与--existing-instance-url互斥 | 二选一必填 |
--existing-instance-url | 指向已存在实例(自托管场景使用),需配合 admin key | 无 |
--existing-instance-admin-key | 已存在实例的 admin key | 无 |
--existing-project-slug | 复用已存在项目(配合 provisioner 使用) | 无 |
--skip-build | 跳过 ScenarioRunner 的构建(turbo build) | 关闭 |
--skip-actions-deploy | 跳过部署 node actions(避免在生产创建过多 AWS Lambda) | 关闭 |
--use-preview-deployments | 预览部署压测模式(配合--num-preview-deployments、--num-preview-deployment-pushes) | 关闭 |
三、对自托管 Convex 进行压测(核心流程)
原文档给出的自托管压测流程分两步,务必按顺序执行。
第 1 步:将 ScenarioRunner 函数推送到自托管后端
cd npm-packages/scenario-runner npx convex deploy --admin-key=<your-admin-key> --url=<your-backend-url>强烈警告(原文档原文强调):不要对生产实例执行此操作!这一步会替换掉你的函数(deploy会整体覆盖该项目的函数代码)。请使用仅用于测试的独立 Convex 后端。
第 2 步:针对自托管后端运行 LoadGenerator
cd ../../crates/load_generator just self-hosted crates/load_generator/workloads/<your-workload>.json \ --existing-instance-url <your-backend-url> \ --existing-instance-admin-key <your-admin-key>just self-hosted展开后的完整命令为(来自 Justfile):
cargo run --release -p load_generator --bin load-generator \ -- --stats-report --once --duration 60 \ crates/load_generator/workloads/<your-workload>.json \ --existing-instance-url <your-backend-url> \ --existing-instance-admin-key <your-admin-key>即:以 release 模式构建运行,持续 60 秒,结束后输出统计报告。从 main.rs 看,当同时提供--existing-instance-url与--existing-instance-admin-key时,会走run_workload(None, backend_url, admin_key, ...)分支,直接对指定实例施压;否则走 provisioner 自动供给分支(生产供给还会加入最长 30 秒的随机 jitter 避免多实例同时抢占)。
关于 rate 与线程两种模式(详见 main.rs 的Mode枚举):
rate(每秒请求数):以固定速率发送请求,如 workloads/prod.json;benchmark(线程数):以指定数量的线程连续发请求,每个线程串行等待上一个请求响应后才发下一个,用于测量后端在最大并发下的极限能力,如 workloads/benchmark_query.json。
运行期间的内部行为
压测启动后,LoadGenerator 会依次执行(对应 main.rs 的run_workload):
- 调用
wait_for_http_health等待后端 HTTP 健康检查通过(最多重试 2 次、每次间隔 250ms),并读取backend_version作为指标标签; - 执行
setup,按 workload 中的num_rows(默认 500)与num_vector_rows(默认 0)向后端写入初始数据; - 构建并 spawn ScenarioRunner 子进程,传入部署 URL、admin key、load-generator 端口与场景 JSON;
- LoadGenerator 启动 HTTP/WebSocket 服务,在
--duration指定的时长内持续接收事件; - 时长结束后向所有 WebSocket 连接发送 Close,kill 掉 ScenarioRunner 子进程,再等待 10 秒让后端处理完剩余日志事件;
- 若指定
--stats-report则打印统计报告;随后调用fail_if_too_many_errors()——如果错误过多,进程以非零状态退出,方便 CI 判断压测是否通过。
四、workload 配置文件详解
workload 是 JSON 文件,结构定义在 main.rs:
struct Workload { name: String, scenarios: Vec<ScenarioConfig>, num_vector_rows: u64, // 初始 setup mutation 写入的向量行数 num_rows: u64, // 初始 setup mutation 写入的消息行数,默认 500 }顶层字段:
| 字段 | 说明 |
|---|---|
name | workload 名称,会作为指标标签load_description(格式为{name}_{duration}s) |
scenarios | 场景数组,每个场景含name、场景专有参数、以及rate或benchmark模式 |
num_vector_rows | 初始化时写入的向量搜索行数(prod.json中为 200) |
num_rows | 初始化时写入的消息行数,默认 500 |
内置场景类型
Scenario枚举(main.rs)支持以下场景,覆盖了从普通函数到订阅、搜索、向量搜索、快照导出、HTTP action 的完整能力面:
场景name | 字段 | 说明 |
|---|---|---|
RunFunction | path(模块:函数名)、fn_type(query/mutation/action) | 按路径调用 ScenarioRunner 中的 Convex 函数,是最通用的场景 |
ObserveInsert | search_indexes(bool) | 建立订阅观察插入事件,可选是否涉及搜索索引 |
Search | — | 执行全文搜索压测 |
VectorSearch | — | 执行向量搜索压测 |
SnapshotExport | — | 触发快照导出压测 |
CloudBackup | — | 触发云备份流程压测(prod.json中 rate 低至 0.00005) |
ManyIntersections | num_subscriptions | 同时建立大量订阅并产生交叉失效(用于压测订阅失效路径) |
HoldSubscriptions | num_subscriptions、hold_duration_secs、invalidation_interval_secs(可选)、num_invalidations(可选) | 长时间持有订阅并按间隔触发失效 |
RunHttpAction | path、method | 通过 HTTP 调用 action(如POST) |
示例 1:prod.json(混合负载,模拟生产流量)
workloads/prod.json 是仓库中最全面的混合负载示例,模拟了生产环境常见的操作组合:带搜索的查询与写入、组件(components)的 query/mutation、搜索索引观察、全文搜索、向量搜索、定时任务(schedule)、HTTP action、快照导出与云备份。片段如下:
{ "name": "prod", "scenarios": [ { "name": "RunFunction", "path": "query_index:queryMessagesWithSearch", "fn_type": "query", "rate": 10 }, { "name": "RunFunction", "path": "update", "fn_type": "mutation", "rate": 2 }, { "name": "ObserveInsert", "search_indexes": true, "rate": 5 }, { "name": "Search", "rate": 6 }, { "name": "VectorSearch", "rate": 5 }, { "name": "RunHttpAction", "path": "streaming", "method": "POST", "rate": 2 }, { "name": "SnapshotExport", "rate": 0.0005 }, { "name": "CloudBackup", "rate": 0.00005 } ], "num_vector_rows": 200 }注意prod.json中SnapshotExport的 rate 为 0.0005(即每 2000 秒一次)、CloudBackup为 0.00005(每 20000 秒一次),这类低频后台任务场景主要是为了模拟真实生产环境中的偶发重操作。
示例 2:benchmark_query.json(线程模式,测极限吞吐)
workloads/benchmark_query.json 展示了 benchmark 模式——用benchmark字段替代rate:
{ "name": "benchmark_query", "scenarios": [ { "name": "RunFunction", "path": "query_index:queryMessagesWithSearch", "fn_type": "query", "benchmark": 80 } ] }含义:启动 80 个线程,每个线程串行地持续发起queryMessagesWithSearch查询,测量后端的极限并发吞吐与延迟。
其他内置 workload
仓库 crates/load_generator/workloads 目录下还提供了针对不同目的的配置,可根据压测目标选用:
light.json:轻量混合负载(含 action、搜索、云备份),适合快速冒烟测试;heavy.json/large.json:高负载 / 大数据量压测;benchmark_insert.json、benchmark_search.json、benchmark_query_and_insert.json:针对单类操作的线程模式极限测试;hold_subscriptions.json、many_intersections.json:订阅与失效路径专项压测;search.json、search_debug.json、vector_search.json:搜索与向量搜索专项;check_dev.json、empty.json:开发/冒烟用;repro_memory_leak.json:内存泄漏复现场景;prod_with_node_actions.json:生产混合负载 + node actions。
五、编写自定义压测场景
原文档提供了"自定义场景"的完整方法:在 npm-packages/scenario-runner/convex 目录中自行编写 Convex 函数,然后在 workload 配置中以RunFunction场景引用它。
约束与步骤
- 函数签名要求:函数名不能带参数(no arguments)。例如
query_index.ts中的queryMessagesWithSearch就符合要求; - 将函数放进
npm-packages/scenario-runner/convex/目录(如新建your_module.ts,导出export const yourFunction = query({ handler: ... })或对应的mutation/action); - 在 workload JSON 中配置一个
RunFunction场景; - 重新
npx convex deploy推送函数,再运行 LoadGenerator 并指向新的 workload 配置。
自定义场景配置模板
{ "name": "your_new_workload", "scenarios": [ { "name": "RunFunction", "path": "<your-new-module>:<your-function-name>", "fn_type": "mutation", "rate": 5 } ] }字段说明:
name:场景名,必须为RunFunction(或其他内置场景名);path:<模块名>:<函数名>,模块名即convex/下.ts文件的 basename;fn_type:mutation(写入)、query(查询)或action(动作)三者之一,需与函数实际类型一致;rate:每秒请求数;若要用线程模式,改为"benchmark": <线程数>。
场景函数示例
以仓库自带的 query_index.ts 为例,其压测函数queryMessagesWithSearch是典型的"带缓存击穿参数"的查询:通过cacheBreaker随机参数在随机 offset 处查询,使请求均匀分布、避免命中缓存,从而压测真实的数据库查询路径:
export const queryMessagesWithSearch = query({ args: CACHE_BREAKER_ARGS, handler: async ({ db }, { cacheBreaker }) => { // 在随机 offset 处查询,均匀分布、不命中缓存 return await queryMessagesHelper( db, "global", cacheBreaker, 10, "messages_with_search", ); }, }); function queryMessagesHelper( db: DatabaseReader, channel: string, rand: number, limit: number, table: MessagesTable, ) { return db .query(table) .withIndex("by_channel_rand", (q) => q.eq("channel", channel).gte("rand", rand), ) .take(limit); }编写自定义场景时可以参考这一模式:如果你的目标是压测缓存未命中路径,可以引入随机参数打破查询缓存;如果目标是压测冷路径或索引路径,则聚焦特定索引查询。其余场景(插入、更新、搜索、向量搜索、定时调度、HTTP action 等)可参考insert.ts、update.ts、search.ts、vectorSearch.ts、schedule.ts、http.ts、openclaurd.ts等现有实现。
新增场景类型的扩展方式
如果内置场景类型不够用,需要同时修改两端(遵循 npm-packages/scenario-runner/README.md 的扩展指南):
- 在 ScenarioRunner 的
index.ts中把场景名加入ScenarioName并接入main控制流; - 编写实现
IScenario接口、继承Scenario基类的类,放入scenarios目录,并从main控制流调用; - 在 LoadGenerator 的
Scenario枚举(main.rs)中新增对应的场景变体。
这样新增的场景就能像内置场景一样被 workload JSON 引用并施加负载。
六、结果解读与指标观测
统计报告
加上--stats-report后,压测结束会打印统计报告。报告内容由 crates/load_generator/src/stats.rs 生成,核心是各类请求的延迟分位数(典型如 p50 / p95 / p99)与错误统计。同时fail_if_too_many_errors()会在错误过多时让进程以非零状态退出——这一点对把压测接入 CI 回归非常有用:可以在每次发布前用固定 workload 对比延迟与错误率是否劣化。
Prometheus 指标导出
使用--metrics-addr参数可启动 Prometheus exporter(如--metrics-addr 0.0.0.0:9100),LoadGenerator 会在该地址暴露指标供采集,代码路径为 performance_stats/exporter.rs 中的register_prometheus_exporter(main.rs)。采集到的指标会带上load_description({workload}_{duration}s)与backend_version标签,方便按 workload 与后端版本维度对比分析。
指标标签
从 main.rs 可见,LoadGenerator 为所有指标附加的关键标签包括:
load_description:格式<workload名>_<时长>s,例如prod_60s;backend_version:被测后端的版本号(来自健康检查接口);- 场景名与函数路径(
metrics::log_target_qps会记录每个场景的目标 QPS)。
有了这些标签,就可以在监控系统中按后端版本、负载类型做横向对比,评估新版本是否引入性能回退。
七、注意事项与最佳实践
综合原文档与源码,进行自托管压测时建议遵守以下原则:
- 使用专用测试实例:
npx convex deploy会整体替换实例上的函数,绝不要对生产实例执行;为压测单独部署一个自托管后端。 - 区分 rate 与 benchmark 模式:需要模拟真实流量节奏时用
rate(每秒请求数);需要测量极限并发能力时用benchmark(线程数)。 - 从轻量 workload 开始:先用
light.json或自定义的少量场景验证链路(部署、setup、事件回传、报告输出)正常,再逐步加大到heavy.json/prod.json级别的混合负载。 - 关注低频后台场景:
SnapshotExport、CloudBackup等场景 rate 极低,主要模拟生产中的偶发重操作,压测时要预留足够时长让它们有机会触发。 - 开启 metrics 导出做长期对比:使用
--metrics-addr暴露 Prometheus 指标,按backend_version标签追踪每次发布后的性能变化。 - CI 集成:利用
--once+--stats-report的一次性运行模式和错误退出机制(fail_if_too_many_errors),把压测作为发布前回归检查的一环。 - 充分预热数据:workload 顶层的
num_rows与num_vector_rows控制 setup 阶段写入的数据量,测试搜索 / 向量搜索 / 大表查询前应保证数据规模接近真实负载。
参考文档索引
- 自托管压测入口文档:self-hosted/advanced/benchmarking.md
- LoadGenerator 官方说明:crates/load_generator/README.md
- ScenarioRunner 说明:npm-packages/scenario-runner/README.md
- 核心实现:crates/load_generator/src/main.rs
- 预置命令:crates/load_generator/Justfile
- 内置 workload 目录:crates/load_generator/workloads
- 压测场景函数:npm-packages/scenario-runner/convex
- 数据库
- 后端
【免费下载链接】convex-backend
The open-source reactive database for app developers
相关推荐
Convex Scheduling 实战:基于 convex-backend 实现 5 秒自毁消息示例应用
Convex Scheduling 实战:基于 convex backend 实现 5 秒自毁消息示例应用 导读 本文基于 convex backend 仓库中
数据库后端Convex Private Demos E2E 测试:基于 Playwright 与 convex-local-backend 的本地端到端测试体系
Convex Private Demos E2E 测试:基于 Playwright 与 convex local backend 的本地端到端测试体系 导读 本
数据库后端Convex TypeScript 与 Schema 实战:基于 convex-backend 仓库的 Typescript 示例应用全解析
Convex TypeScript 与 Schema 实战:基于 convex backend 仓库的 Typescript 示例应用全解析 导读 本文以 co
数据库后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考