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
- 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
- indistinguishable
- +2.1% (database-bound)
- indistinguishable
- indistinguishable (database-bound)
- indistinguishable
- indistinguishable (database-bound)
- -3%
- -2.5%
- indistinguishable
- indistinguishable
- indistinguishable
- indistinguishable (database-bound)
- indistinguishable
- indistinguishable (database-bound)
- indistinguishable
- indistinguishable (database-bound)
- indistinguishable
- indistinguishable (database-bound)
Show the numbers as a table
| Scenario | Raw sqlx loop | Crate by hand | RustStream service | Overhead |
|---|---|---|---|---|
| row lock claim, JSON decode into a small struct, delete each | 6,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 each | 5,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 each | 29,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 each | 3,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 each | 4,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 each | 6,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 each | 5,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 each | 26,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 each | 29,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) |
- Without the outbox
- Outbox written by hand
- The crate's outbox
- rounds, best to slowest
- best round
- median round
- spread of the rounds
- indistinguishable
- indistinguishable
- +76.2%
- +75.9% (indistinguishable over by hand)
- +86.3%
- +86.5% (indistinguishable over by hand)
- +84.9%
- +85.2% (indistinguishable over by hand)
Show the numbers as a table
| Scenario | Without the outbox | Outbox written by hand | The crate's outbox | Overhead | Over the outbox by hand |
|---|---|---|---|---|---|
| outbox plugin, an untracked message through both layers | 53,058 msg/s (52,720) | - | 53,160 msg/s (52,178) | indistinguishable | - |
| outbox plugin, a tracked publish from outside a handler | 66,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 marked | 64,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 marked | 54,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 |
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.
Show the numbers as a table
| Scenario | Instructions | Allocations | Cold start |
|---|---|---|---|
| row lock claim, JSON decode into a small struct, delete each | 44,796.1 | 32 | 334,632 / 355 |
| row lock claim, reply inserted through this crate's default publisher, delete each | 66,613.4 | 44 | 337,677 / 353 |
| row lock claim in batches of 64, delete each | 14,281.5 | 12.6 | 334,178 / 358 |
| lease claim, JSON decode into a small struct, delete each | 51,541.4 | 28 | 397,875 / 393 |
| advisory lock claim, JSON decode into a small struct, delete each | 66,800.8 | 47.1 | 421,572 / 494 |
| by-name subscription, row lock claim, delete each | 47,474.3 | 32 | 356,127 / 359 |
| row mode, the row decoded by the driver, delete each | 43,674.9 | 32 | 338,529 / 364 |
| repository publish, one insert each | 23,890.9 | 15 | 144,459 / 128 |
| routed publish, one insert each | 23,429 | 15 | 145,266 / 129 |
| outbox plugin, an untracked message through both layers | 7,238.2 | 6 | 251,239 / 250 |
| outbox plugin, a tracked publish from outside a handler | 23,232.9 | 15 | 183,040 / 161 |
| outbox plugin, a tracked delivery fetched and marked | 51,016.7 | 30 | 280,164 / 271 |
| outbox plugin, a tracked round trip: recorded, delivered, fetched and marked | 75,639.3 | 42.1 | 328,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.
| Scenario | Service, instructions | Raw loop, instructions | Service, allocations | Raw loop, allocations |
|---|---|---|---|---|
| row lock claim, JSON decode into a small struct, delete each | 44,796.1 | 40,310.6 | 32 | 33 |
| row lock claim, reply inserted through this crate's default publisher, delete each | 66,613.4 | 60,551.5 | 44 | 45 |
| row lock claim in batches of 64, delete each | 14,281.5 | 12,300.7 | 12.6 | 12.4 |
| lease claim, JSON decode into a small struct, delete each | 51,541.4 | 47,133 | 28 | 28 |
| advisory lock claim, JSON decode into a small struct, delete each | 66,800.8 | 58,844.8 | 47.1 | 47.1 |
| by-name subscription, row lock claim, delete each | 47,474.3 | 40,384.5 | 32 | 33 |
| row mode, the row decoded by the driver, delete each | 43,674.9 | 39,836.3 | 32 | 33 |
| repository publish, one insert each | 23,890.9 | 20,830.8 | 15 | 12 |
| routed publish, one insert each | 23,429 | 20,830.8 | 15 | 12 |
| Scenario | Instructions (no outbox / by hand / outbox) | Allocations (no outbox / by hand / outbox) |
|---|---|---|
| outbox plugin, an untracked message through both layers | 6,788.2 / - / 7,238.2 | 6 / - / 6 |
| outbox plugin, a tracked publish from outside a handler | 742.2 / 22,349.9 / 23,232.9 | 2 / 16 / 15 |
| outbox plugin, a tracked delivery fetched and marked | 4,877.7 / 47,021.6 / 51,016.7 | 7 / 30 / 30 |
| outbox plugin, a tracked round trip: recorded, delivered, fetched and marked | 6,821.3 / 68,347.8 / 75,639.3 | 6 / 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.
- 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
- indistinguishable
- indistinguishable
- indistinguishable
- +18.5%
- indistinguishable
- +19%
- indistinguishable
- indistinguishable
- indistinguishable
- indistinguishable
- indistinguishable
- +21.6%
Show the numbers as a table
| Scenario | Raw sqlx loop | Crate by hand | RustStream service | Overhead |
|---|---|---|---|---|
| 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% |
- 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
- indistinguishable
- +16.4%
- indistinguishable
- +25.5%
- indistinguishable
- +17.8%
- indistinguishable
- indistinguishable
- indistinguishable
- indistinguishable
- indistinguishable
- indistinguishable
Show the numbers as a table
| Scenario | Raw sqlx loop | Crate by hand | RustStream service | Overhead |
|---|---|---|---|---|
| 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 |
- 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
- +3.3%
- +3.3%
- +4.2%
- +63.9%
- +3%
- +65.5%
- +5%
- indistinguishable
- indistinguishable
- indistinguishable
- indistinguishable
- indistinguishable
Show the numbers as a table
| Scenario | Raw sqlx loop | Crate by hand | RustStream service | Overhead |
|---|---|---|---|---|
| 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 |
- 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
- +85.2%
- +85.8% (+3.7% over by hand)
- +50.8%
- +51.9% (indistinguishable over by hand)
- +37.2%
- +39.7% (+3.9% over by hand)
Show the numbers as a table
| Scenario | Without the outbox | Outbox written by hand | The crate's outbox | Overhead | Over 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
| Processor | AMD Ryzen 9 7900X 12-Core Processor |
|---|---|
| Architecture | x86_64 |
| Frequency | 427-5912 MHz |
| Cores | 12 physical, 24 logical |
| Memory | 62.4 GiB |
| Memory speed | unknown |
| Operating system | Linux 7.2.11-x64v3-xanmod1-1 |
| Databases | 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 |
| Round trip | 17.4 us (median of 2000 `SELECT 1` round trips on one Postgres connection) |
| Compiler | 1.99.0 |
| sqlx | 0.9.0 |
| Build profile | bench, inheriting release (opt-level = 3, lto = fat, codegen-units = 16, debug = line-tables-only) |
| Features | ruststream-sqlx inbox,outbox,postgres,mysql,sqlite,chrono; ruststream macros,json,memory; ruststream-fred default |
| RUSTFLAGS | none (the recipe clears RUSTFLAGS, so the numbers are not tied to this CPU) |
| Valgrind | 3.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.