跳到主要内容
英文版本为准

本页译自英文,由模型翻译。若与英文原文存在差异,以英文原文为准。

基准测试

ruststream-sqlx 在每条消息上的开销:与裸 sqlx 循环对照的吞吐量,作为插件的 outbox,以及它自身代码的指令数和内存分配次数。

与裸 sqlx 循环对照​

第一张图在测试环境的 PostgreSQL 上把每个场景用三种方式运行。裸 sqlx 循环 是手写的循环,它在以同样方式构建的连接池上,按同样的顺序执行 Broker 对同一张表执行的那些语句;这些语句取自方言,因此两者不会走样。手动驱动 crate 用一个自己的循环驱动本 crate 的订阅或发布者,没有运行时。RustStream 服务 是用户写的服务:一张 #[derive(Inbox)] 表、一个 #[subscriber] 处理器和应用。第一项差值是本 crate 在它所执行的语句之外的开销,第二项是运行时在其上增加的开销。

每个循环画成一条横条,从最佳一轮延伸到最慢的一轮,圆点标出最佳一轮,缺口标出中位一轮。坐标轴的零点是裸循环的最佳一轮,圆点在零点右侧表示更慢。空心圆点表示该循环与裸循环的差异小于各轮的波动范围。图表下方的表格列出最佳一轮,括号里是中位数。

每条消息至少要付出一次领取、一次结算和一次提交,每一项都是一次到数据库的往返。标为 受数据库限制 的行,其裸循环在每条消息上至少有一半时间在等待这些往返。这时本 crate 和运行时的工作发生在裸循环同样要付出的等待之内,该行给出的是它们开销的下限,而不是对开销的测量。

作为插件的 outbox​

outbox 可以与任意 Broker 配合,所以把它作为普通应用的一项开销来测量:同一个应用的三个变体,经 ruststream-fred 运行在测试环境的 Redis Pub/Sub 和 PostgreSQL 上。不用 outbox 是用户不使用 outbox 时写的应用。手写的 outbox 是同一个应用,在每次发布前插入记录,再在处理器中取出并标记记录,用裸 sqlx 实现。crate 自带的 outbox 是同一个应用,使用本 crate 的注册表、两个中间件层和重发。与不用 outbox 对照,图表显示跟踪一条消息本身的开销;与手写的 outbox 对照,它显示本 crate 的机制在用户无论如何都会执行的语句之外增加了多少。

代码的开销​

开销图统计一条消息在服务线程上的开销:稳态下 callgrind 测得的指令数和 DHAT 测得的内存分配次数,连接测试环境的 PostgreSQL。计数包括运行时、本 crate,以及 sqlx 驱动程序在该线程上的编码和解码;数据库服务器是另一个进程,不计入。冷启动 是启动服务一次性付出的开销:打开连接池的连接、Broker 的启动检查和第一次投递。outbox 的计数让同样的应用运行在 MemoryBroker 上,因此其中不含任何传输的开销。

结果​

inbox各循环比裸循环最佳一轮低多少
  • 裸 sqlx 循环
  • 手动驱动 crate
  • RustStream 服务
  • 各轮,从最佳到最慢
  • 最佳一轮
  • 中位一轮
  • 轮次的波动范围
  • 无法区分

ruststream-sqlx 0.7.0 on ruststream 0.7.1, AMD Ryzen 9 7900X 12-Core Processor, Linux 7.2.11-x64v3-xanmod1-1, postgres:17-alpine, mysql:8.0, redis:7-alpine in Docker on the host network, without durability; SQLite bundled with sqlx, on a local file in WAL mode; the outbox over MemoryBroker in the code table and over Redis Pub/Sub in the wall clock, 测量于 2026-10-18. 这些数字是怎样测得的

row lock claim, JSON decode into a small struct, delete each
  • 无法区分
  • +2.1% (受数据库限制)
row lock claim, reply inserted through this crate's default publisher, delete each
  • 无法区分
  • 无法区分 (受数据库限制)
row lock claim in batches of 64, delete each
  • 无法区分
  • 无法区分 (受数据库限制)
lease claim, JSON decode into a small struct, delete each
  • -3%
  • -2.5%
advisory lock claim, JSON decode into a small struct, delete each
  • 无法区分
  • 无法区分
by-name subscription, row lock claim, delete each
  • 无法区分
  • 无法区分 (受数据库限制)
row mode, the row decoded by the driver, delete each
  • 无法区分
  • 无法区分 (受数据库限制)
repository publish, one insert each
  • 无法区分
  • 无法区分 (受数据库限制)
routed publish, one insert each
  • 无法区分
  • 无法区分 (受数据库限制)
以表格显示数字
场景裸 sqlx 循环手动驱动 crateRustStream 服务额外开销
row lock claim, JSON decode into a small struct, delete each6,149 msg/s (6,103)6,136 msg/s (5,986) (比裸循环 无法区分)6,018 msg/s (5,996)+2.1% (受数据库限制)
row lock claim, reply inserted through this crate's default publisher, delete each5,150 msg/s (4,984)5,103 msg/s (5,092) (比裸循环 无法区分)5,007 msg/s (4,937)无法区分 (受数据库限制)
row lock claim in batches of 64, delete each29,198 msg/s (29,006)29,043 msg/s (28,629) (比裸循环 无法区分)28,724 msg/s (28,676)无法区分 (受数据库限制)
lease claim, JSON decode into a small struct, delete each3,125 msg/s (3,098)3,219 msg/s (3,193) (比裸循环 -3%)3,202 msg/s (3,177)-2.5%
advisory lock claim, JSON decode into a small struct, delete each4,611 msg/s (4,572)4,558 msg/s (4,462) (比裸循环 无法区分)4,565 msg/s (4,295)无法区分
by-name subscription, row lock claim, delete each6,089 msg/s (5,910)6,025 msg/s (5,773) (比裸循环 无法区分)5,980 msg/s (5,784)无法区分 (受数据库限制)
row mode, the row decoded by the driver, delete each5,805 msg/s (5,771)5,769 msg/s (5,662) (比裸循环 无法区分)5,759 msg/s (5,742)无法区分 (受数据库限制)
repository publish, one insert each26,723 msg/s (26,721)27,814 msg/s (27,485) (比裸循环 无法区分)27,640 msg/s (26,771)无法区分 (受数据库限制)
routed publish, one insert each29,182 msg/s (27,701)27,947 msg/s (26,567) (比裸循环 无法区分)27,295 msg/s (26,896)无法区分 (受数据库限制)
outbox 插件各循环比不用 outbox 的应用最佳一轮低多少
  • 不用 outbox
  • 手写的 outbox
  • crate 自带的 outbox
  • 各轮,从最佳到最慢
  • 最佳一轮
  • 中位一轮
  • 轮次的波动范围
  • 无法区分
outbox plugin, an untracked message through both layers
  • 无法区分
outbox plugin, a tracked publish from outside a handler
  • +76.2%
  • +75.9% (比手写 无法区分)
outbox plugin, a tracked delivery fetched and marked
  • +86.3%
  • +86.5% (比手写 无法区分)
outbox plugin, a tracked round trip: recorded, delivered, fetched and marked
  • +84.9%
  • +85.2% (比手写 无法区分)
以表格显示数字
场景不用 outbox手写的 outboxcrate 自带的 outbox额外开销比手写 outbox
outbox plugin, an untracked message through both layers53,058 msg/s (52,720)-53,160 msg/s (52,178)无法区分-
outbox plugin, a tracked publish from outside a handler66,275 msg/s (66,203)15,761 msg/s (15,425) (比不用 outbox +76.2%)15,961 msg/s (15,395)+75.9%无法区分
outbox plugin, a tracked delivery fetched and marked64,200 msg/s (61,442)8,821 msg/s (8,600) (比不用 outbox +86.3%)8,682 msg/s (8,635)+86.5%无法区分
outbox plugin, a tracked round trip: recorded, delivered, fetched and marked54,357 msg/s (53,776)8,191 msg/s (8,143) (比不用 outbox +84.9%)8,044 msg/s (8,025)+85.2%无法区分
代码的开销

AMD Ryzen 9 7900X 12-Core Processor, x86_64, 427-5912 MHz, 12 physical, 24 logical, 62.4 GiB, unknown, Linux 7.2.11-x64v3-xanmod1-1, 1.99.0, 3.25.1, bench, inheriting release (opt-level = 3, lto = fat, codegen-units = 16, debug = line-tables-only), ruststream-sqlx inbox,outbox,postgres,mysql,sqlite,chrono; ruststream macros,json,memory; ruststream-fred default. ruststream-sqlx 0.7.0, ruststream 0.7.1, 测量于 2026-10-18.

指令数
内存分配
row lock claim, JSON decode into a small struct, delete each
44,796.1
32
row lock claim, reply inserted through this crate's default publisher, delete each
66,613.4
44
row lock claim in batches of 64, delete each
14,281.5
12.6
lease claim, JSON decode into a small struct, delete each
51,541.4
28
advisory lock claim, JSON decode into a small struct, delete each
66,800.8
47.1
by-name subscription, row lock claim, delete each
47,474.3
32
row mode, the row decoded by the driver, delete each
43,674.9
32
repository publish, one insert each
23,890.9
15
routed publish, one insert each
23,429
15
outbox plugin, an untracked message through both layers
7,238.2
6
outbox plugin, a tracked publish from outside a handler
23,232.9
15
outbox plugin, a tracked delivery fetched and marked
51,016.7
30
outbox plugin, a tracked round trip: recorded, delivered, fetched and marked
75,639.3
42.1
以表格显示数字
场景指令数内存分配冷启动
row lock claim, JSON decode into a small struct, delete each44,796.132334,632 / 355
row lock claim, reply inserted through this crate's default publisher, delete each66,613.444337,677 / 353
row lock claim in batches of 64, delete each14,281.512.6334,178 / 358
lease claim, JSON decode into a small struct, delete each51,541.428397,875 / 393
advisory lock claim, JSON decode into a small struct, delete each66,800.847.1421,572 / 494
by-name subscription, row lock claim, delete each47,474.332356,127 / 359
row mode, the row decoded by the driver, delete each43,674.932338,529 / 364
repository publish, one insert each23,890.915144,459 / 128
routed publish, one insert each23,42915145,266 / 129
outbox plugin, an untracked message through both layers7,238.26251,239 / 250
outbox plugin, a tracked publish from outside a handler23,232.915183,040 / 161
outbox plugin, a tracked delivery fetched and marked51,016.730280,164 / 271
outbox plugin, a tracked round trip: recorded, delivered, fetched and marked75,639.342.1328,380 / 311

下面的计数与它们各自的对照循环并列:inbox 对照裸 sqlx 循环,outbox 对照不用 outbox 的应用和手写 outbox 的应用。

场景服务,指令数裸循环,指令数服务,内存分配裸循环,内存分配
row lock claim, JSON decode into a small struct, delete each44,796.140,310.63233
row lock claim, reply inserted through this crate's default publisher, delete each66,613.460,551.54445
row lock claim in batches of 64, delete each14,281.512,300.712.612.4
lease claim, JSON decode into a small struct, delete each51,541.447,1332828
advisory lock claim, JSON decode into a small struct, delete each66,800.858,844.847.147.1
by-name subscription, row lock claim, delete each47,474.340,384.53233
row mode, the row decoded by the driver, delete each43,674.939,836.33233
repository publish, one insert each23,890.920,830.81512
routed publish, one insert each23,42920,830.81512
场景指令数(不用 outbox / 手写 / outbox)内存分配(不用 outbox / 手写 / outbox)
outbox plugin, an untracked message through both layers6,788.2 / - / 7,238.26 / - / 6
outbox plugin, a tracked publish from outside a handler742.2 / 22,349.9 / 23,232.92 / 16 / 15
outbox plugin, a tracked delivery fetched and marked4,877.7 / 47,021.6 / 51,016.77 / 30 / 30
outbox plugin, a tracked round trip: recorded, delivered, fetched and marked6,821.3 / 68,347.8 / 75,639.36 / 39 / 42.1

吞吐量​

在每种数据库上,用一个以 workers(n) 挂载的订阅排空一张已填满的表,连接池有 16 个连接,分别使用逐条投递和每批 64 条。PostgreSQL 和 MySQL 采用行锁方式。SQLite 没有行锁,因此采用租约方式。裸循环在一个连接池上运行 n 个任务,每个任务领取、读取并删除,直到领取返回空。本 crate 的循环在同一个连接池上运行 n 个订阅,每个订阅使用自己的 Broker。服务运行一个订阅,由它为所有 worker 领取。outbox 的单元格在 outbox 的三个变体中,都以 workers(n) 挂载中继和接收端。

Postgres,行锁方式各循环比裸循环最佳一轮低多少
  • 裸 sqlx 循环
  • 手动驱动 crate
  • RustStream 服务
  • 各轮,从最佳到最慢
  • 最佳一轮
  • 中位一轮
  • 轮次的波动范围
  • 无法区分

ruststream-sqlx 0.7.0 on ruststream 0.7.1, 测量于 2026-10-18. 这些数字是怎样测得的

逐条投递, workers(1)
  • 无法区分
  • 无法区分
逐条投递, workers(4)
  • 无法区分
  • +18.5%
逐条投递, workers(8)
  • 无法区分
  • +19%
每批 64 条, workers(1)
  • 无法区分
  • 无法区分
每批 64 条, workers(4)
  • 无法区分
  • 无法区分
每批 64 条, workers(8)
  • 无法区分
  • +21.6%
以表格显示数字
场景裸 sqlx 循环手动驱动 crateRustStream 服务额外开销
逐条投递, workers(1)6,606 msg/s (6,534)6,498 msg/s (6,345) (比裸循环 无法区分)6,519 msg/s (6,489)无法区分
逐条投递, workers(4)21,422 msg/s (20,667)20,365 msg/s (20,352) (比裸循环 无法区分)17,465 msg/s (16,553)+18.5%
逐条投递, workers(8)35,388 msg/s (35,358)35,013 msg/s (34,770) (比裸循环 无法区分)28,664 msg/s (28,526)+19%
每批 64 条, workers(1)31,095 msg/s (30,671)30,127 msg/s (30,047) (比裸循环 无法区分)29,910 msg/s (29,658)无法区分
每批 64 条, workers(4)100,358 msg/s (94,995)96,291 msg/s (93,160) (比裸循环 无法区分)94,292 msg/s (93,376)无法区分
每批 64 条, workers(8)154,114 msg/s (153,177)154,622 msg/s (151,889) (比裸循环 无法区分)120,889 msg/s (119,422)+21.6%
MySQL,行锁方式各循环比裸循环最佳一轮低多少
  • 裸 sqlx 循环
  • 手动驱动 crate
  • RustStream 服务
  • 各轮,从最佳到最慢
  • 最佳一轮
  • 中位一轮
  • 轮次的波动范围
  • 无法区分

ruststream-sqlx 0.7.0 on ruststream 0.7.1, 测量于 2026-10-18. 这些数字是怎样测得的

逐条投递, workers(1)
  • 无法区分
  • +16.4%
逐条投递, workers(4)
  • 无法区分
  • +25.5%
逐条投递, workers(8)
  • 无法区分
  • +17.8%
每批 64 条, workers(1)
  • 无法区分
  • 无法区分
每批 64 条, workers(4)
  • 无法区分
  • 无法区分
每批 64 条, workers(8)
  • 无法区分
  • 无法区分
以表格显示数字
场景裸 sqlx 循环手动驱动 crateRustStream 服务额外开销
逐条投递, workers(1)7,626 msg/s (7,409)6,791 msg/s (6,498) (比裸循环 无法区分)6,376 msg/s (6,329)+16.4%
逐条投递, workers(4)17,830 msg/s (16,166)16,316 msg/s (15,688) (比裸循环 无法区分)13,281 msg/s (13,131)+25.5%
逐条投递, workers(8)21,885 msg/s (21,792)19,070 msg/s (15,962) (比裸循环 无法区分)17,994 msg/s (17,839)+17.8%
每批 64 条, workers(1)36,152 msg/s (35,674)35,752 msg/s (34,773) (比裸循环 无法区分)35,228 msg/s (35,088)无法区分
每批 64 条, workers(4)107,344 msg/s (107,082)102,923 msg/s (86,929) (比裸循环 无法区分)99,924 msg/s (95,386)无法区分
每批 64 条, workers(8)158,847 msg/s (132,613)147,257 msg/s (139,066) (比裸循环 无法区分)143,767 msg/s (120,226)无法区分
SQLite,租约方式各循环比裸循环最佳一轮低多少
  • 裸 sqlx 循环
  • 手动驱动 crate
  • RustStream 服务
  • 各轮,从最佳到最慢
  • 最佳一轮
  • 中位一轮
  • 轮次的波动范围
  • 无法区分

ruststream-sqlx 0.7.0 on ruststream 0.7.1, 测量于 2026-10-18. 这些数字是怎样测得的

逐条投递, workers(1)
  • +3.3%
  • +3.3%
逐条投递, workers(4)
  • +4.2%
  • +63.9%
逐条投递, workers(8)
  • +3%
  • +65.5%
每批 64 条, workers(1)
  • +5%
  • 无法区分
每批 64 条, workers(4)
  • 无法区分
  • 无法区分
每批 64 条, workers(8)
  • 无法区分
  • 无法区分
以表格显示数字
场景裸 sqlx 循环手动驱动 crateRustStream 服务额外开销
逐条投递, workers(1)14,073 msg/s (13,850)13,609 msg/s (13,565) (比裸循环 +3.3%)13,611 msg/s (13,599)+3.3%
逐条投递, workers(4)14,453 msg/s (14,266)13,846 msg/s (13,728) (比裸循环 +4.2%)5,218 msg/s (4,723)+63.9%
逐条投递, workers(8)14,244 msg/s (14,181)13,814 msg/s (13,733) (比裸循环 +3%)4,916 msg/s (4,754)+65.5%
每批 64 条, workers(1)33,167 msg/s (33,147)31,521 msg/s (31,281) (比裸循环 +5%)33,092 msg/s (31,648)无法区分
每批 64 条, workers(4)34,067 msg/s (33,800)35,105 msg/s (33,255) (比裸循环 无法区分)34,839 msg/s (33,646)无法区分
每批 64 条, workers(8)35,446 msg/s (34,634)34,829 msg/s (33,519) (比裸循环 无法区分)35,544 msg/s (35,537)无法区分
Postgres,经 Redis Pub/Sub 的 outbox 往返各循环比不用 outbox 的应用最佳一轮低多少
  • 不用 outbox
  • 手写的 outbox
  • crate 自带的 outbox
  • 各轮,从最佳到最慢
  • 最佳一轮
  • 中位一轮
  • 轮次的波动范围
  • 无法区分

ruststream-sqlx 0.7.0 on ruststream 0.7.1, 测量于 2026-10-18. 这些数字是怎样测得的

逐条投递, workers(1)
  • +85.2%
  • +85.8% (比手写 +3.7%)
逐条投递, workers(4)
  • +50.8%
  • +51.9% (比手写 无法区分)
逐条投递, workers(8)
  • +37.2%
  • +39.7% (比手写 +3.9%)
以表格显示数字
场景不用 outbox手写的 outboxcrate 自带的 outbox额外开销比手写 outbox
逐条投递, workers(1)54,941 msg/s (52,927)8,128 msg/s (8,097) (比不用 outbox +85.2%)7,826 msg/s (7,663)+85.8%+3.7%
逐条投递, workers(4)51,743 msg/s (51,332)25,440 msg/s (25,275) (比不用 outbox +50.8%)24,866 msg/s (24,810)+51.9%无法区分
逐条投递, workers(8)50,650 msg/s (45,681)31,813 msg/s (31,242) (比不用 outbox +37.2%)30,560 msg/s (29,582)+39.7%+3.9%

机器​

处理器AMD Ryzen 9 7900X 12-Core Processor
架构x86_64
频率427-5912 MHz
核心数12 physical, 24 logical
内存62.4 GiB
内存速度unknown
操作系统Linux 7.2.11-x64v3-xanmod1-1
数据库postgres:17-alpine, mysql:8.0, redis:7-alpine in Docker on the host network, without durability; SQLite bundled with sqlx, on a local file in WAL mode; the outbox over MemoryBroker in the code table and over Redis Pub/Sub in the wall clock
往返时间17.4 us (median of 2000 `SELECT 1` round trips on one Postgres connection)
编译器1.99.0
sqlx0.9.0
构建配置bench, inheriting release (opt-level = 3, lto = fat, codegen-units = 16, debug = line-tables-only)
特性ruststream-sqlx inbox,outbox,postgres,mysql,sqlite,chrono; ruststream macros,json,memory; ruststream-fred default
RUSTFLAGSnone (the recipe clears RUSTFLAGS, so the numbers are not tied to this CPU)
Valgrind3.25.1

数字来自本版本的 docs/benchmarks/results.json,由基准测试命令写入,并作为 benchmarks/results.json 与页面一起发布。方法论说明它们是怎样测得的,数字说明可以从中得出什么结论。