Spindle vs the field
A linearized Matrix homeserver: an append-only log per room, materialized state, and no state resolution on the hot path. Every milestone, the same client operations are measured against the field — Synapse, Dendrite, and the two Rust servers of the conduwuit lineage — and wins, noise and losses are all published from the committed raw numbers.
0cells faster
0within noise
0slower — investigated
vs ContinuwuityRust · RocksDB
24 faster3 within noise0 slower
the conduwuit lineage (Conduit → conduwuit → continuwuity): the performance bar, and the comparison that matters most.
vs DendriteGo · SQLite per component, NATS in-process
24 faster0 within noise0 slower
Element's second-generation server, a different design again (components on an event bus); the server an operator reaches for when Synapse is too heavy and a Rust build is not on the table.
vs SynapsePython · Postgres on the shared runner, SQLite in the milestone sittings
27 faster0 within noise0 slower
the reference implementation and what most deployments run. Each sitting's provenance line says which database it ran on: the milestone sittings used SQLite, which its own docs call a development database, so those columns are a floor for Synapse; the shared-runner sittings from September 2026 run it on Postgres, as its docs say to.
vs TuwunelRust · RocksDB
21 faster6 within noise0 slower
the other conduwuit descendant, by conduwuit's original author; built from source at its tag because its release assets are gated.
Why Spindle exists
The problem it starts from
A Matrix room is a directed graph of events, and every homeserver in a
room appends to that graph on its own. That is what makes Matrix federated
and what makes it expensive: two servers can extend the graph at once, the
result is a fork, and answering "what is the state of this room?"
means running state resolution over the branches. Every established
server does that work somewhere on the path a client's request takes, and
its cost grows with the room.
The bet
Spindle keeps the graph at the door and a line behind it. Each
room is one append-only log; each entry carries the content address of the
state after it; forks arriving over federation are collapsed once, on the
way in, and never taxed on reads. Sending, syncing, paginating and reading
state become index arithmetic over that log. If the bet is right, the
operations a client performs thousands of times a day should cost less and,
more to the point, should not grow with the room.
What this page can and cannot show
It can show whether the bet pays on the client path, measured by one
driver, on one idle machine, against servers configured the way their own
documentation suggests for a single node. It cannot show a federated room
under contention, where forks are real and Spindle's collapse-at-the-door
has a cost of its own; that is measured elsewhere (the interop rigs) and
not yet as a benchmark. Synapse runs on SQLite here, its development
database, so its column is a floor for Synapse rather than a ceiling. And
nothing here has run in production. Read the wins as evidence for a
design, not for a deployment.
Why the shape of the storage shows up in every row
Watch the same six events arrive at both designs, then watch one read
ask "what is the state?" The loop below plays the whole
difference: on one side the answer must be computed; on the other it was
already written down.
A conventional homeserver
Rooms are a directed graph of events. Forks are normal, so answering
"what is the state?" means running state resolution over the
branches — work that grows with the room and sits on the hot path of
sync, send, and join.
Spindle
Each room is an append-only log with materialized state: every
entry carries the content address (BLAKE3 hash trie) of the state after
it. "The state now" is one read; history is a range; nothing resolves on
the hot path. Federation forks are collapsed at the door, bounded, and
never taxed on reads.
append-only log · Kafka, Raft
content-addressed state · git, Merkle trees
HAMT snapshots · Clojure, CHAMP
single-writer order · LMAX, TigerBeetle
losses published · every red cell links its investigation
That is the bet these pages test. The comparisons below measure the same
client operations against the same workloads on Synapse (the reference
implementation), on Continuwuity and Tuwunel (the two Rust siblings, both
descended from Conduit) and, from the M7 sittings on, Dendrite (Element's
Go server) — and when a cell goes the wrong way, the roadmap's rule is that
it gets investigated, not explained away.
Latest sitting — m7-progress-2
developer machine · Intel(R) Xeon(R) Processor @ 2.10GHz, 4 cores, kernel 6.18.44-fc-v24 · 3 rounds, 2026-09-06 · spindle @ 5707ffecece5
measured: spindle 0.1.0 @ 5707ffecece5, synapse 1.160.0, continuwuity 26.8.1 (ab3c05d), tuwunel 1.9.0, dendrite 0.15.2+e546df2
the committed files (15)
continuwuity (m7-progress-2.continuwuity.r1.json) · continuwuity (m7-progress-2.continuwuity.r2.json) · continuwuity (m7-progress-2.continuwuity.r3.json) · dendrite (m7-progress-2.dendrite.r1.json) · dendrite (m7-progress-2.dendrite.r2.json) · dendrite (m7-progress-2.dendrite.r3.json) · spindle (m7-progress-2.spindle.r1.json) · spindle (m7-progress-2.spindle.r2.json) · spindle (m7-progress-2.spindle.r3.json) · synapse (m7-progress-2.synapse.r1.json) · synapse (m7-progress-2.synapse.r2.json) · synapse (m7-progress-2.synapse.r3.json) · tuwunel (m7-progress-2.tuwunel.r1.json) · tuwunel (m7-progress-2.tuwunel.r2.json) · tuwunel (m7-progress-2.tuwunel.r3.json)
hover a server to isolate it; click to hide it from every chart and table on this page. Hiding changes what is drawn, never what was measured.
The /context of an event far back in history. On a linear log an old event's neighbourhood is an index range, not a graph walk.
A local user joins: one membership event through the same append path as any other, authorized against materialized state.
A /messages page walking backwards. The linear log makes this a bounded range read — no topological sort at request time.
One event appended to a room. Spindle assigns the next index in the room's linear log and folds one state snapshot forward; a DAG server must pick extremities, and its cost tends to grow with the room.
The request Element X makes where classic clients call /sync: a sorted room-list window plus per-room state. The room-list question is one point read per room on Spindle's storage.
The full current state of a room. The log's head entry carries the state root; answering is one rehydration of a hash trie.
The same request when a room does have something waiting. It takes a different path from the empty poll -- the server skips rooms with no new events entirely -- so the two are measured separately rather than averaged into one misleading number.
A client's first sync: full state plus recent timeline for every room. Materialized state makes 'the state now' one content-addressed read instead of a resolution.
The request a running client makes over and over: since=<token>, and the answer is usually “nothing”. It is the most common request a homeserver ever serves, and until M5 no sitting measured it at all.
Every cell
Measured over 3 rounds per server. The large figure in a cell is the median over rounds against the median over rounds; the small range under it is the band the rounds allow — their fastest round against our slowest, to their slowest against our fastest. A cell is called only when that band excludes 1.0×, which is the same as saying the two servers' rounds separate: win our slowest round beat their fastest · overlapping the ranges cross, so the difference is not resolved by this many rounds, whatever the medians say · loss our fastest round lost to their slowest — every such cell links to its investigation, because the roadmap treats it as a defect until explained. Hover any cell for both servers' medians and observed ranges in milliseconds.
That rule bounds the false-call rate for one cell, and this table has 105. Two identical servers separate by luck 10.0% of the time at 3 rounds, so about 10.5 of these cells should be called by chance alone — against 96 actually called. The arithmetic cannot say which ones, so a call marked † is one that stands alone: the same operation is not called the same way at any other size. A cost that is real in a per-item measure normally shows across the size axis, so an isolated call is the shape a chance separation takes. Read those as unconfirmed rather than as results (#183).
Latest shared-runner sitting — ci-20261010
Run by the weekly bench-sitting workflow on a GitHub-hosted runner: the same driver, the same field, the same rules, on a host that is slower, noisier and shared. It keeps the page current between milestone sittings and cannot be compared cell for cell with them -- the two hosts are two instruments. Tally on this host: 99 faster · 4 within noise · 2 slower.
github-actions/ubuntu-24.04 (shared runner) · Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz, 4 cores, kernel 6.17.0-1022-azure · 3 rounds, 2026-10-10 · spindle @ 0f2c1434fff1
measured: spindle 0.1.0 0f2c143 @ 0f2c1434fff1, synapse 1.161.0 (b=main,0f2c143) on postgres, continuwuity 26.9.0 (a237672), tuwunel 1.9.2, dendrite 0.15.2+e546df2
the committed files (15)
continuwuity (ci-20261010.continuwuity.r1.json) · continuwuity (ci-20261010.continuwuity.r2.json) · continuwuity (ci-20261010.continuwuity.r3.json) · dendrite (ci-20261010.dendrite.r1.json) · dendrite (ci-20261010.dendrite.r2.json) · dendrite (ci-20261010.dendrite.r3.json) · spindle (ci-20261010.spindle.r1.json) · spindle (ci-20261010.spindle.r2.json) · spindle (ci-20261010.spindle.r3.json) · synapse (ci-20261010.synapse.r1.json) · synapse (ci-20261010.synapse.r2.json) · synapse (ci-20261010.synapse.r3.json) · tuwunel (ci-20261010.tuwunel.r1.json) · tuwunel (ci-20261010.tuwunel.r2.json) · tuwunel (ci-20261010.tuwunel.r3.json)
hover a server to isolate it; click to hide it from every chart and table on this page. Hiding changes what is drawn, never what was measured.
The /context of an event far back in history. On a linear log an old event's neighbourhood is an index range, not a graph walk.
A local user joins: one membership event through the same append path as any other, authorized against materialized state.
A /messages page walking backwards. The linear log makes this a bounded range read — no topological sort at request time.
One event appended to a room. Spindle assigns the next index in the room's linear log and folds one state snapshot forward; a DAG server must pick extremities, and its cost tends to grow with the room.
The request Element X makes where classic clients call /sync: a sorted room-list window plus per-room state. The room-list question is one point read per room on Spindle's storage.
The full current state of a room. The log's head entry carries the state root; answering is one rehydration of a hash trie.
The same request when a room does have something waiting. It takes a different path from the empty poll -- the server skips rooms with no new events entirely -- so the two are measured separately rather than averaged into one misleading number.
A client's first sync: full state plus recent timeline for every room. Materialized state makes 'the state now' one content-addressed read instead of a resolution.
The request a running client makes over and over: since=<token>, and the answer is usually “nothing”. It is the most common request a homeserver ever serves, and until M5 no sitting measured it at all.
Every cell
Measured over 3 rounds per server. The large figure in a cell is the median over rounds against the median over rounds; the small range under it is the band the rounds allow — their fastest round against our slowest, to their slowest against our fastest. A cell is called only when that band excludes 1.0×, which is the same as saying the two servers' rounds separate: win our slowest round beat their fastest · overlapping the ranges cross, so the difference is not resolved by this many rounds, whatever the medians say · loss our fastest round lost to their slowest — every such cell links to its investigation, because the roadmap treats it as a defect until explained. Hover any cell for both servers' medians and observed ranges in milliseconds.
That rule bounds the false-call rate for one cell, and this table has 105. Two identical servers separate by luck 10.0% of the time at 3 rounds, so about 10.5 of these cells should be called by chance alone — against 101 actually called. The arithmetic cannot say which ones, so a call marked † is one that stands alone: the same operation is not called the same way at any other size. A cost that is real in a per-item measure normally shows across the size axis, so an isolated call is the shape a chance separation takes. Read those as unconfirmed rather than as results (#183).
ci-20260906 — 100 faster · 5 within noise · 0 slower
github-actions/ubuntu-24.04 (shared runner) · AMD EPYC 7763 64-Core Processor, 4 cores, kernel 6.17.0-1022-azure · 3 rounds, 2026-09-06 · spindle @ 35988f5c5705
measured: spindle 0.1.0 @ 35988f5c5705, synapse 1.160.0 (b=main,35988f5) on postgres, continuwuity 26.8.1 (ab3c05d), tuwunel 1.9.0, dendrite 0.15.2+e546df2
the committed files (15)
continuwuity (ci-20260906.continuwuity.r1.json) · continuwuity (ci-20260906.continuwuity.r2.json) · continuwuity (ci-20260906.continuwuity.r3.json) · dendrite (ci-20260906.dendrite.r1.json) · dendrite (ci-20260906.dendrite.r2.json) · dendrite (ci-20260906.dendrite.r3.json) · spindle (ci-20260906.spindle.r1.json) · spindle (ci-20260906.spindle.r2.json) · spindle (ci-20260906.spindle.r3.json) · synapse (ci-20260906.synapse.r1.json) · synapse (ci-20260906.synapse.r2.json) · synapse (ci-20260906.synapse.r3.json) · tuwunel (ci-20260906.tuwunel.r1.json) · tuwunel (ci-20260906.tuwunel.r2.json) · tuwunel (ci-20260906.tuwunel.r3.json)
Measured over 3 rounds per server. The large figure in a cell is the median over rounds against the median over rounds; the small range under it is the band the rounds allow — their fastest round against our slowest, to their slowest against our fastest. A cell is called only when that band excludes 1.0×, which is the same as saying the two servers' rounds separate: win our slowest round beat their fastest · overlapping the ranges cross, so the difference is not resolved by this many rounds, whatever the medians say · loss our fastest round lost to their slowest — every such cell links to its investigation, because the roadmap treats it as a defect until explained. Hover any cell for both servers' medians and observed ranges in milliseconds.
That rule bounds the false-call rate for one cell, and this table has 105. Two identical servers separate by luck 10.0% of the time at 3 rounds, so about 10.5 of these cells should be called by chance alone — against 100 actually called. The arithmetic cannot say which ones, so a call marked † is one that stands alone: the same operation is not called the same way at any other size. A cost that is real in a per-item measure normally shows across the size axis, so an isolated call is the shape a chance separation takes. Read those as unconfirmed rather than as results (#183).
Earlier sittings
The trail matters: each milestone re-measures the same operations, so a regression shows up as a cell that changed color between sittings.
m7-progress — 75 faster · 6 within noise · 0 slower
Measured over 3 rounds per server. The large figure in a cell is the median over rounds against the median over rounds; the small range under it is the band the rounds allow — their fastest round against our slowest, to their slowest against our fastest. A cell is called only when that band excludes 1.0×, which is the same as saying the two servers' rounds separate: win our slowest round beat their fastest · overlapping the ranges cross, so the difference is not resolved by this many rounds, whatever the medians say · loss our fastest round lost to their slowest — every such cell links to its investigation, because the roadmap treats it as a defect until explained. Hover any cell for both servers' medians and observed ranges in milliseconds.
That rule bounds the false-call rate for one cell, and this table has 81. Two identical servers separate by luck 10.0% of the time at 3 rounds, so about 8.1 of these cells should be called by chance alone — against 75 actually called. The arithmetic cannot say which ones, so a call marked † is one that stands alone: the same operation is not called the same way at any other size. A cost that is real in a per-item measure normally shows across the size axis, so an isolated call is the shape a chance separation takes. Read those as unconfirmed rather than as results (#183).
m4-final — 44 faster · 19 within noise · 0 slower
1 round(s) per server — not resolved. Three rounds a side is the minimum that means anything: if two servers were identical, all of one side's rounds landing below all of the other's happens by chance 2/C(2n, n) of the time — one in three at two rounds, one in ten at three. With no spread to read, the cells below are coloured by the repeatability this host was measured to have: six rounds of the same binary moved the median cell 1.38×, and 21 of 21 cells varied by more than the ±10% band this page used to use. So: ≥1.38× · 0.72–1.38× · ≤0.72×. A grey cell is not a tie — it is a difference this sitting cannot see, and only more rounds can (#171). The large ratios are unaffected. Hover any cell for the raw milliseconds.
m3-final — 51 faster · 12 within noise · 0 slower
1 round(s) per server — not resolved. Three rounds a side is the minimum that means anything: if two servers were identical, all of one side's rounds landing below all of the other's happens by chance 2/C(2n, n) of the time — one in three at two rounds, one in ten at three. With no spread to read, the cells below are coloured by the repeatability this host was measured to have: six rounds of the same binary moved the median cell 1.38×, and 21 of 21 cells varied by more than the ±10% band this page used to use. So: ≥1.38× · 0.72–1.38× · ≤0.72×. A grey cell is not a tie — it is a difference this sitting cannot see, and only more rounds can (#171). The large ratios are unaffected. Hover any cell for the raw milliseconds.
Nothing in this sitting reads as a loss under the band above. The investigations below were written when it did, and are kept because they are the evidence for the conclusion, not a footnote to it.
Investigated — Sliding sync (MSC4186): 0.77× vs Continuwuity at 3,200 events, and 0.85× in the same day's discarded loaded run — repeatable by the two-sitting rule, so it got the full second look. Both servers were probed live minutes after the sitting, same client, same instant, two shapes: like-for-like the gap does not exist (creator shape 0.844 ms vs 0.854 ms, parity; the driver's exact observer shape 0.785 ms vs 0.874 ms, Spindle 1.11× faster). The two sitting legs caught opposite sides of the machine's same-day swing. No fix ships because no defect was found; the cell keeps its measured value and links here — the honest kind of red.
Investigated — Initial /sync: 0.89× vs Tuwunel at 200 events in this sitting — and 1.21× in the same day's discarded run, with 800 and 3,200 in noise both times. A cell that flips sign between sittings hours apart is run-to-run variance, published as measured.
m3-progress — 53 faster · 10 within noise · 0 slower
1 round(s) per server — not resolved. Three rounds a side is the minimum that means anything: if two servers were identical, all of one side's rounds landing below all of the other's happens by chance 2/C(2n, n) of the time — one in three at two rounds, one in ten at three. With no spread to read, the cells below are coloured by the repeatability this host was measured to have: six rounds of the same binary moved the median cell 1.38×, and 21 of 21 cells varied by more than the ±10% band this page used to use. So: ≥1.38× · 0.72–1.38× · ≤0.72×. A grey cell is not a tie — it is a difference this sitting cannot see, and only more rounds can (#171). The large ratios are unaffected. Hover any cell for the raw milliseconds.
Nothing in this sitting reads as a loss under the band above. The investigations below were written when it did, and are kept because they are the evidence for the conclusion, not a footnote to it.
Investigated — Sliding sync (MSC4186): 0.90× and 0.88× vs Continuwuity at 800 and 3,200 events — and the same cell sat at 0.83× in the M2 close-out, so two sittings agreed this was repeatable, not noise. A component probe on the live bench server found the room list's recency sort reading each room's head event body from the store and parsing its JSON on every request, for one i64. #126 moved the sort key into memory, refreshed by the append that changes it; re-measured on the same idle machine the cells recover to 0.96× and 1.00×, and Spindle's own growth curve flattens from 1.28× to 1.13× across a 16× room-size increase.
m2-final — 51 faster · 12 within noise · 0 slower
1 round(s) per server — not resolved. Three rounds a side is the minimum that means anything: if two servers were identical, all of one side's rounds landing below all of the other's happens by chance 2/C(2n, n) of the time — one in three at two rounds, one in ten at three. With no spread to read, the cells below are coloured by the repeatability this host was measured to have: six rounds of the same binary moved the median cell 1.38×, and 21 of 21 cells varied by more than the ±10% band this page used to use. So: ≥1.38× · 0.72–1.38× · ≤0.72×. A grey cell is not a tie — it is a difference this sitting cannot see, and only more rounds can (#171). The large ratios are unaffected. Hover any cell for the raw milliseconds.
Nothing in this sitting reads as a loss under the band above. The investigations below were written when it did, and are kept because they are the evidence for the conclusion, not a footnote to it.
Investigated — Sliding sync (MSC4186): 0.87× vs Continuwuity at 3,200 events was the one real loss of the M2 close-out, and the only curve growing with room size. Bisecting a live server pinned it in one probe: the unread counter was reading every event body after the receipt floor. #113 replaced that walk with two binary searches over a per-room sender index — 11.79 ms → 1.00 ms on the pathological case — and the M3 rows below show the cell recovered.
Investigated — Read room state: 0.85× vs Tuwunel at 200 events, and 0.93× again at M3 progress — the one cell Tuwunel held across two sittings, so the investigation started in their tree. Their /state serves each event through RocksDB's block cache; ours paid a room-lock acquisition, a body read and a JSON parse per state event, per request. Component probes showed their state machinery was never actually faster — their per-request pipeline is just leaner. #129 caches the rendered /state body under its BLAKE3 state root (content-addressed, so a hit is provably current and a root mismatch is the only invalidation); re-measured against the live Tuwunel binary the cell flips to 1.91× and 1.33× in Spindle's favour.
m2-progress — 36 faster · 6 within noise · 0 slower
1 round(s) per server — not resolved. Three rounds a side is the minimum that means anything: if two servers were identical, all of one side's rounds landing below all of the other's happens by chance 2/C(2n, n) of the time — one in three at two rounds, one in ten at three. With no spread to read, the cells below are coloured by the repeatability this host was measured to have: six rounds of the same binary moved the median cell 1.38×, and 21 of 21 cells varied by more than the ±10% band this page used to use. So: ≥1.38× · 0.72–1.38× · ≤0.72×. A grey cell is not a tie — it is a difference this sitting cannot see, and only more rounds can (#171). The large ratios are unaffected. Hover any cell for the raw milliseconds.
m1-spindle-vs-synapse — 15 faster · 0 within noise · 0 slower
1 round(s) per server — not resolved. Three rounds a side is the minimum that means anything: if two servers were identical, all of one side's rounds landing below all of the other's happens by chance 2/C(2n, n) of the time — one in three at two rounds, one in ten at three. With no spread to read, the cells below are coloured by the repeatability this host was measured to have: six rounds of the same binary moved the median cell 1.38×, and 21 of 21 cells varied by more than the ±10% band this page used to use. So: ≥1.38× · 0.72–1.38× · ≤0.72×. A grey cell is not a tie — it is a difference this sitting cannot see, and only more rounds can (#171). The large ratios are unaffected. Hover any cell for the raw milliseconds.
m1-spindle-vs-continuwuity — 15 faster · 3 within noise · 0 slower
1 round(s) per server — not resolved. Three rounds a side is the minimum that means anything: if two servers were identical, all of one side's rounds landing below all of the other's happens by chance 2/C(2n, n) of the time — one in three at two rounds, one in ten at three. With no spread to read, the cells below are coloured by the repeatability this host was measured to have: six rounds of the same binary moved the median cell 1.38×, and 21 of 21 cells varied by more than the ±10% band this page used to use. So: ≥1.38× · 0.72–1.38× · ≤0.72×. A grey cell is not a tie — it is a difference this sitting cannot see, and only more rounds can (#171). The large ratios are unaffected. Hover any cell for the raw milliseconds.
concurrency-perroom — 0 faster · 0 within noise · 0 slower
2 round(s) per server — not resolved. Three rounds a side is the minimum that means anything: if two servers were identical, all of one side's rounds landing below all of the other's happens by chance 2/C(2n, n) of the time — one in three at two rounds, one in ten at three. With no spread to read, the cells below are coloured by the repeatability this host was measured to have: six rounds of the same binary moved the median cell 1.38×, and 21 of 21 cells varied by more than the ±10% band this page used to use. So: ≥1.38× · 0.72–1.38× · ≤0.72×. A grey cell is not a tie — it is a difference this sitting cannot see, and only more rounds can (#171). The large ratios are unaffected. Hover any cell for the raw milliseconds.
concurrency — 8 faster · 0 within noise · 0 slower
1 round(s) per server — not resolved. Three rounds a side is the minimum that means anything: if two servers were identical, all of one side's rounds landing below all of the other's happens by chance 2/C(2n, n) of the time — one in three at two rounds, one in ten at three. With no spread to read, the cells below are coloured by the repeatability this host was measured to have: six rounds of the same binary moved the median cell 1.38×, and 21 of 21 cells varied by more than the ±10% band this page used to use. So: ≥1.38× · 0.72–1.38× · ≤0.72×. A grey cell is not a tie — it is a difference this sitting cannot see, and only more rounds can (#171). The large ratios are unaffected. Hover any cell for the raw milliseconds.
Method
- Same host, same sitting, same load. All servers in one
run on one idle machine — a leg once ran beside a compiler and read ~25%
slow across the board, so idleness is part of the method, and it is checked,
not assumed.
- Cold databases, verified binaries. Every leg starts
from an empty store, and the serving process is pgrep-verified before
measuring — a stale process once served an old binary on the right port.
- Curves, not points. Every operation is measured at
200, 800 and 3,200 events per room, because the failure mode worth catching
is the cost that grows with the room. Room size has a second axis —
joined members — and the sweep held it at two until M5, which hid
a sliding-window read that grew linearly with the member list. Membership
is now its own sweep, on its own chart, labelled by what it counts.
- A sitting is several rounds, not one. Each round
takes the median of 25 samples after warmup, per cell; a sitting repeats
that five times per server by default (three is the minimum that means
anything — see below), reversing the order of the servers every round so a
drift in the machine becomes spread the page can see rather than bias it
cannot. Every round is committed exactly as the driver wrote it
(
docs/benchmarks/data/<group>.<server>.r<N>.json);
a published cell is the median across rounds with the observed range
beside it, and this page is regenerated from those files and cannot change
a measurement.
- Each server runs as its own documentation suggests for one
node, with its rate limits lifted. Synapse from a virtualenv on
SQLite; Continuwuity from its static release binary and Tuwunel built from
source at its tag, both on RocksDB with a registration token because they
refuse open registration; Dendrite from source with SQLite per component,
its NATS bus in-process, federation off and open registration on. Nothing
is tuned, on any side, and the launch recipe is committed
(
scripts/bench-servers.sh). A server whose binary is absent
from a sitting is absent from that sitting's table, never carried forward
from an older one.
- Hiding a server changes the drawing, not the data.
The chips above the charts take a server out of the frame -- Synapse's
column is often ten times the Rust servers' and flattens their lines to
the axis -- and the charts rescale and the tables drop the column. The
committed files, the tallies and every ratio are computed before the page
knows what you chose, and a log scale is one click away when you would
rather keep everyone in view.
- Losses publish with the same prominence as wins.
A cell is called, either way, only when the two servers' rounds separate,
and a loss links to its investigation.
- The instrument's own spread is on the page, because it is
wider than the band this page used to colour by. Six rounds of
the same binary on the same idle host moved the median cell by 1.38× and
the worst by 2.80×, so every cell varied more between runs of identical
code than the ±10% that used to decide its colour. That is why the band
was replaced rather than tuned
(#171): a cell
is now decided by whether the two servers' own round-to-round ranges
overlap, and the range is printed under every ratio so a reader can see
what it had to clear. Sittings collected before #171 are one round each,
have no range to read, and are coloured by the measured 1.38× floor
instead — labelled unresolved, and never given the count and marker that
only the separation rule can justify.
Versions measured, ports, registration quirks and the full narrative per
sitting:
docs/benchmarks.md.