Skip to main content

Benchmarks

What ruststream-sqlx costs on every message: throughput against a raw sqlx loop, the outbox as a plugin, and the instructions and allocations of its code.

Against a raw sqlx loop​

The first chart runs each scenario three ways on the stand's PostgreSQL. Raw sqlx loop is a loop written by hand that runs the statements the broker runs for the same table, in the same order, on a pool built the same way; it takes them from the dialect, so the two cannot drift apart. Crate by hand drives this crate's subscription or publisher with a loop of its own and no runtime. RustStream service is the service a user writes: a #[derive(Inbox)] table, a #[subscriber] handler and the app. The first gap is what this crate costs over the statements it runs, and the second is what the runtime adds on top.

Each loop is a bar from its best round to its slowest, with a dot at the best round and a notch at the median. The zero of the axis is the raw loop's best round, and a dot right of zero is slower. A hollow dot is a loop whose difference from the raw loop is smaller than the spread of the rounds. The table under the chart prints the best round with the median in parentheses.

Every message costs at least a claim, a settlement and a commit, each a round trip to the database. A row marked database-bound is one whose raw loop spent at least half of each message waiting on those round trips. The crate and the runtime then do their work inside a wait the raw loop pays as well, and the row is a lower bound on their cost rather than a measurement of it.

The outbox as a plugin​

The outbox works over any broker, so it is measured as a cost to an ordinary app: one app in three variants, over the stand's Redis Pub/Sub through ruststream-fred and the stand's PostgreSQL. Without the outbox is the app a user writes without one. Outbox written by hand is the same app with the record inserted before each publish, then fetched and marked in the handler, in raw sqlx. The crate's outbox is the same app with this crate's registry, its two layers and the republish. Against no outbox the chart shows what tracking a message costs at all; against the outbox by hand it shows what the crate's machinery adds to the statements a user would run anyway.

Cost of the code​

The cost chart counts what one message costs on the service's thread: instructions under callgrind and allocations under DHAT, in the steady state, against the stand's PostgreSQL. The count holds the runtime, this crate and sqlx's driver encoding and decoding on that thread; the database server is another process and is not counted. Cold start is what starting the service costs once: opening the pool's connections, the broker's startup checks and the first delivery. The outbox's counts run the same apps over MemoryBroker, so no transport is in their number.

Results​

The inboxHow far each loop falls below the raw loop's best round
  • Raw sqlx loop
  • Crate by hand
  • RustStream service
  • rounds, best to slowest
  • best round
  • median round
  • spread of the rounds
  • indistinguishable

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, measured 2026-10-18. How the numbers were taken

row lock claim, JSON decode into a small struct, delete each
  • indistinguishable
  • +2.1% (database-bound)
row lock claim, reply inserted through this crate's default publisher, delete each
  • indistinguishable
  • indistinguishable (database-bound)
row lock claim in batches of 64, delete each
  • indistinguishable
  • indistinguishable (database-bound)
lease claim, JSON decode into a small struct, delete each
  • -3%
  • -2.5%
advisory lock claim, JSON decode into a small struct, delete each
  • indistinguishable
  • indistinguishable
by-name subscription, row lock claim, delete each
  • indistinguishable
  • indistinguishable (database-bound)
row mode, the row decoded by the driver, delete each
  • indistinguishable
  • indistinguishable (database-bound)
repository publish, one insert each
  • indistinguishable
  • indistinguishable (database-bound)
routed publish, one insert each
  • indistinguishable
  • indistinguishable (database-bound)
Show the numbers as a table
ScenarioRaw sqlx loopCrate by handRustStream serviceOverhead
row lock claim, JSON decode into a small struct, delete each6,149 msg/s (6,103)6,136 msg/s (5,986) (indistinguishable over the raw loop)6,018 msg/s (5,996)+2.1% (database-bound)
row lock claim, reply inserted through this crate's default publisher, delete each5,150 msg/s (4,984)5,103 msg/s (5,092) (indistinguishable over the raw loop)5,007 msg/s (4,937)indistinguishable (database-bound)
row lock claim in batches of 64, delete each29,198 msg/s (29,006)29,043 msg/s (28,629) (indistinguishable over the raw loop)28,724 msg/s (28,676)indistinguishable (database-bound)
lease claim, JSON decode into a small struct, delete each3,125 msg/s (3,098)3,219 msg/s (3,193) (-3% over the raw loop)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) (indistinguishable over the raw loop)4,565 msg/s (4,295)indistinguishable
by-name subscription, row lock claim, delete each6,089 msg/s (5,910)6,025 msg/s (5,773) (indistinguishable over the raw loop)5,980 msg/s (5,784)indistinguishable (database-bound)
row mode, the row decoded by the driver, delete each5,805 msg/s (5,771)5,769 msg/s (5,662) (indistinguishable over the raw loop)5,759 msg/s (5,742)indistinguishable (database-bound)
repository publish, one insert each26,723 msg/s (26,721)27,814 msg/s (27,485) (indistinguishable over the raw loop)27,640 msg/s (26,771)indistinguishable (database-bound)
routed publish, one insert each29,182 msg/s (27,701)27,947 msg/s (26,567) (indistinguishable over the raw loop)27,295 msg/s (26,896)indistinguishable (database-bound)
The outbox pluginHow far each loop falls below the best round of the app without the outbox
  • Without the outbox
  • Outbox written by hand
  • The crate's outbox
  • rounds, best to slowest
  • best round
  • median round
  • spread of the rounds
  • indistinguishable
outbox plugin, an untracked message through both layers
  • indistinguishable
outbox plugin, a tracked publish from outside a handler
  • +76.2%
  • +75.9% (indistinguishable over by hand)
outbox plugin, a tracked delivery fetched and marked
  • +86.3%
  • +86.5% (indistinguishable over by hand)
outbox plugin, a tracked round trip: recorded, delivered, fetched and marked
  • +84.9%
  • +85.2% (indistinguishable over by hand)
Show the numbers as a table
ScenarioWithout the outboxOutbox written by handThe crate's outboxOverheadOver the outbox by hand
outbox plugin, an untracked message through both layers53,058 msg/s (52,720)-53,160 msg/s (52,178)indistinguishable-
outbox plugin, a tracked publish from outside a handler66,275 msg/s (66,203)15,761 msg/s (15,425) (+76.2% over no outbox)15,961 msg/s (15,395)+75.9%indistinguishable
outbox plugin, a tracked delivery fetched and marked64,200 msg/s (61,442)8,821 msg/s (8,600) (+86.3% over no outbox)8,682 msg/s (8,635)+86.5%indistinguishable
outbox plugin, a tracked round trip: recorded, delivered, fetched and marked54,357 msg/s (53,776)8,191 msg/s (8,143) (+84.9% over no outbox)8,044 msg/s (8,025)+85.2%indistinguishable
Cost of the code

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, measured 2026-10-18.

Instructions
Allocations
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
Show the numbers as a table
ScenarioInstructionsAllocationsCold start
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

The counts beside the loops they are measured against: the raw sqlx loop for the inbox, and the app without the outbox and with one written by hand for the outbox.

ScenarioService, instructionsRaw loop, instructionsService, allocationsRaw loop, allocations
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
ScenarioInstructions (no outbox / by hand / outbox)Allocations (no outbox / by hand / 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

Throughput​

A filled table drained on each database, on a pool of 16 connections, by one subscription mounted with workers(n), single deliveries or batches of 64. PostgreSQL and MySQL take the row lock form. SQLite takes the lease form, because it has no row locks. The raw loop runs n tasks on one pool, each claiming, reading and deleting until its claim comes back empty. The crate's loop runs n subscriptions on the same pool, each on a broker of its own. The service runs one subscription, which claims for all of its workers. The outbox cells mount the relay and the sink with workers(n) in each of the outbox's three variants.

Postgres, row lock formHow far each loop falls below the raw loop's best round
  • Raw sqlx loop
  • Crate by hand
  • RustStream service
  • rounds, best to slowest
  • best round
  • median round
  • spread of the rounds
  • indistinguishable

ruststream-sqlx 0.7.0 on ruststream 0.7.1, measured 2026-10-18. How the numbers were taken

one at a time, workers(1)
  • indistinguishable
  • indistinguishable
one at a time, workers(4)
  • indistinguishable
  • +18.5%
one at a time, workers(8)
  • indistinguishable
  • +19%
batches of 64, workers(1)
  • indistinguishable
  • indistinguishable
batches of 64, workers(4)
  • indistinguishable
  • indistinguishable
batches of 64, workers(8)
  • indistinguishable
  • +21.6%
Show the numbers as a table
ScenarioRaw sqlx loopCrate by handRustStream serviceOverhead
one at a time, workers(1)6,606 msg/s (6,534)6,498 msg/s (6,345) (indistinguishable over the raw loop)6,519 msg/s (6,489)indistinguishable
one at a time, workers(4)21,422 msg/s (20,667)20,365 msg/s (20,352) (indistinguishable over the raw loop)17,465 msg/s (16,553)+18.5%
one at a time, workers(8)35,388 msg/s (35,358)35,013 msg/s (34,770) (indistinguishable over the raw loop)28,664 msg/s (28,526)+19%
batches of 64, workers(1)31,095 msg/s (30,671)30,127 msg/s (30,047) (indistinguishable over the raw loop)29,910 msg/s (29,658)indistinguishable
batches of 64, workers(4)100,358 msg/s (94,995)96,291 msg/s (93,160) (indistinguishable over the raw loop)94,292 msg/s (93,376)indistinguishable
batches of 64, workers(8)154,114 msg/s (153,177)154,622 msg/s (151,889) (indistinguishable over the raw loop)120,889 msg/s (119,422)+21.6%
MySQL, row lock formHow far each loop falls below the raw loop's best round
  • Raw sqlx loop
  • Crate by hand
  • RustStream service
  • rounds, best to slowest
  • best round
  • median round
  • spread of the rounds
  • indistinguishable

ruststream-sqlx 0.7.0 on ruststream 0.7.1, measured 2026-10-18. How the numbers were taken

one at a time, workers(1)
  • indistinguishable
  • +16.4%
one at a time, workers(4)
  • indistinguishable
  • +25.5%
one at a time, workers(8)
  • indistinguishable
  • +17.8%
batches of 64, workers(1)
  • indistinguishable
  • indistinguishable
batches of 64, workers(4)
  • indistinguishable
  • indistinguishable
batches of 64, workers(8)
  • indistinguishable
  • indistinguishable
Show the numbers as a table
ScenarioRaw sqlx loopCrate by handRustStream serviceOverhead
one at a time, workers(1)7,626 msg/s (7,409)6,791 msg/s (6,498) (indistinguishable over the raw loop)6,376 msg/s (6,329)+16.4%
one at a time, workers(4)17,830 msg/s (16,166)16,316 msg/s (15,688) (indistinguishable over the raw loop)13,281 msg/s (13,131)+25.5%
one at a time, workers(8)21,885 msg/s (21,792)19,070 msg/s (15,962) (indistinguishable over the raw loop)17,994 msg/s (17,839)+17.8%
batches of 64, workers(1)36,152 msg/s (35,674)35,752 msg/s (34,773) (indistinguishable over the raw loop)35,228 msg/s (35,088)indistinguishable
batches of 64, workers(4)107,344 msg/s (107,082)102,923 msg/s (86,929) (indistinguishable over the raw loop)99,924 msg/s (95,386)indistinguishable
batches of 64, workers(8)158,847 msg/s (132,613)147,257 msg/s (139,066) (indistinguishable over the raw loop)143,767 msg/s (120,226)indistinguishable
SQLite, lease formHow far each loop falls below the raw loop's best round
  • Raw sqlx loop
  • Crate by hand
  • RustStream service
  • rounds, best to slowest
  • best round
  • median round
  • spread of the rounds
  • indistinguishable

ruststream-sqlx 0.7.0 on ruststream 0.7.1, measured 2026-10-18. How the numbers were taken

one at a time, workers(1)
  • +3.3%
  • +3.3%
one at a time, workers(4)
  • +4.2%
  • +63.9%
one at a time, workers(8)
  • +3%
  • +65.5%
batches of 64, workers(1)
  • +5%
  • indistinguishable
batches of 64, workers(4)
  • indistinguishable
  • indistinguishable
batches of 64, workers(8)
  • indistinguishable
  • indistinguishable
Show the numbers as a table
ScenarioRaw sqlx loopCrate by handRustStream serviceOverhead
one at a time, workers(1)14,073 msg/s (13,850)13,609 msg/s (13,565) (+3.3% over the raw loop)13,611 msg/s (13,599)+3.3%
one at a time, workers(4)14,453 msg/s (14,266)13,846 msg/s (13,728) (+4.2% over the raw loop)5,218 msg/s (4,723)+63.9%
one at a time, workers(8)14,244 msg/s (14,181)13,814 msg/s (13,733) (+3% over the raw loop)4,916 msg/s (4,754)+65.5%
batches of 64, workers(1)33,167 msg/s (33,147)31,521 msg/s (31,281) (+5% over the raw loop)33,092 msg/s (31,648)indistinguishable
batches of 64, workers(4)34,067 msg/s (33,800)35,105 msg/s (33,255) (indistinguishable over the raw loop)34,839 msg/s (33,646)indistinguishable
batches of 64, workers(8)35,446 msg/s (34,634)34,829 msg/s (33,519) (indistinguishable over the raw loop)35,544 msg/s (35,537)indistinguishable
Postgres, outbox round trip over Redis Pub/SubHow far each loop falls below the best round of the app without the outbox
  • Without the outbox
  • Outbox written by hand
  • The crate's outbox
  • rounds, best to slowest
  • best round
  • median round
  • spread of the rounds
  • indistinguishable

ruststream-sqlx 0.7.0 on ruststream 0.7.1, measured 2026-10-18. How the numbers were taken

one at a time, workers(1)
  • +85.2%
  • +85.8% (+3.7% over by hand)
one at a time, workers(4)
  • +50.8%
  • +51.9% (indistinguishable over by hand)
one at a time, workers(8)
  • +37.2%
  • +39.7% (+3.9% over by hand)
Show the numbers as a table
ScenarioWithout the outboxOutbox written by handThe crate's outboxOverheadOver the outbox by hand
one at a time, workers(1)54,941 msg/s (52,927)8,128 msg/s (8,097) (+85.2% over no outbox)7,826 msg/s (7,663)+85.8%+3.7%
one at a time, workers(4)51,743 msg/s (51,332)25,440 msg/s (25,275) (+50.8% over no outbox)24,866 msg/s (24,810)+51.9%indistinguishable
one at a time, workers(8)50,650 msg/s (45,681)31,813 msg/s (31,242) (+37.2% over no outbox)30,560 msg/s (29,582)+39.7%+3.9%

The machine​

ProcessorAMD Ryzen 9 7900X 12-Core Processor
Architecturex86_64
Frequency427-5912 MHz
Cores12 physical, 24 logical
Memory62.4 GiB
Memory speedunknown
Operating systemLinux 7.2.11-x64v3-xanmod1-1
Databasespostgres: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
Round trip17.4 us (median of 2000 `SELECT 1` round trips on one Postgres connection)
Compiler1.99.0
sqlx0.9.0
Build profilebench, inheriting release (opt-level = 3, lto = fat, codegen-units = 16, debug = line-tables-only)
Featuresruststream-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

The numbers come from docs/benchmarks/results.json of this version, written by the benchmark recipes, and published beside the pages as benchmarks/results.json. Methodology says how they were taken, and Numbers what they allow you to conclude.