What it is. The messaging layer fused onto Facts: a durable subject is a stream in the column store. Publishing a message and persisting it are the same operation — there is no broker process, no second system for history, no hop between "the queue" and "the database". Subscribers ride live pushes; anyone who falls behind or joins late reads the log directly. Kafka, fused.
The problem it kills. Brokered messaging pays the broker tax twice: every live message crosses to a server and back, and every replay of history is a stream of per-message round trips. Relay's consumers replay history at engine speed — tens of millions of messages a second — because "catching up" is a columnar scan, not a network conversation.
Patterns, not subsystems. Request/reply and streaming responses are a 16-byte convention on top of pub/sub — an ephemeral inbox and a correlation id — not a bolted-on RPC runtime. Across machines, nodes form a full mesh over Nio with credit-based flow control; a consumer that misses messages self-heals from the origin's log, exactly once, in order.
Talk to us ⟶NATS is the closest architectural rival — subjects, req/reply, durable streams via JetStream. Same machine, rival-favorable configs (separate pub/sub connections, pipelined publishes, file-backed JetStream with a deep ack window), throughput measured as sustained delivered messages with identical pacing on both sides.
| Scenario | Relay · embedded | Relay · mesh¹ | NATS |
|---|---|---|---|
| Live pub→sub, sustained | ~6M msg/s | 2.4M msg/s¹ | ~1.3M msg/s core |
| Durable pub→sub, sustained (persisted) | ~4M msg/s | — | ~0.15M msg/s JetStream² |
| Publish→handler latency, p50 | 1–3 µs | — | ~11 µs |
| Request/reply round trip, p50 | 3–7 µs | — | ~20 µs |
| Replay persisted history | ~50M msg/s | — | ~1.3M msg/s JetStream |
Read it straight. The embedded column is an architectural comparison, not a wire shootout — Relay lives inside your process, NATS always crosses to a server. That gap is the product. NATS is fast and battle-tested; where it fits, it fits. Relay's bet is that most systems don't need a separate broker at all — and pay dearly for having one.
Test bed: the drylane demo box — bare-metal Ubuntu,
32 threads, .NET 10 Release x64, data on NVMe, nats-server v2.14 (-js, file
storage), NATS.Net 2.x, loopback. Ranges are 3 runs on a box that is also serving the
live demos, so throughput carries some contention. ¹ Mesh = two RelayNodes over
loopback Nio (Windows dev-box figure; box re-measure pending). ² A JetStream
publish-ack is a stronger per-message contract than Relay's append-then-periodic-flush
— part of that gap is semantics, and we say so. Full harness + methodology in
samples/Relay.CompetitiveBench.
home.chat.*) resolved at subscribe time; a
subject created later splices in before its first message can be missed.A real two-node cluster, one button. Kill the leader and watch the surviving consumer's delivered counter climb straight through the failover — zero gaps, zero duplicates, the same subscriber that never reconnected.
Break the cluster ⟶Relay is embedded and .NET-first — it wins by deleting the broker, which also defines where it doesn't fit:
Zao packs the message, Relay routes and persists it, Facts is the log it lives in, Nio carries it between machines. One stack, no seams — the same bytes from your struct to the wire to the column store.
info@drylane.io ⟶