1
0

Add the protocol-comparison module

This commit is contained in:
2026-09-04 00:52:18 +05:30
parent 5224afdad2
commit d56c60824e
32 changed files with 1710 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# `protocol-comparison` — RSocket vs gRPC vs WebSocket, same use case, measured
Companion project for
[**RSocket vs gRPC vs WebSocket on Spring Boot 4.1: When Each One Wins**](https://ankurm.com/rsocket-vs-grpc-vs-websocket-spring-boot-4-1/)
on ankurm.com.
**One application serves the same two operations over all three protocols.** Same JVM, same heap,
same data generator, same five fields — so a difference in the numbers is a difference in the
transports rather than in three separately-written demos.
[`./scripts/run-all.sh`](scripts/run-all.sh) re-measures everything under
[`docs/output/`](docs/output/).
## Versions
| | Version | Notes |
|---|---|---|
| JDK | 25 (Temurin 25.0.4.1+1) | current LTS |
| Spring Boot | 4.1.1 | latest GA of the 4.1 line |
| Spring Framework | 7.0.9 | Boot-managed |
| grpc-java | **1.83.1** | Boot-managed. Boot 4.1.0 pinned 1.80.0 — it moved |
| protobuf-java | **4.35.1** | Boot-managed. Boot 4.1.0 pinned 4.34.2 |
| rsocket-java | 1.1.5 | Boot-managed |
| Spring gRPC | 1.1.1 | the programming model; separate version from Boot's |
| Jackson | 3.1.5 (`tools.jackson`) | plus `jackson-dataformat-cbor`, RSocket's default |
Read from `repo1.maven.org/.../maven-metadata.xml` and Boot's own `spring-boot-dependencies` POM.
## Quickstart
```bash
./scripts/run-all.sh # six tests, then re-measure docs/output/
mvn test # the same without the capture
mvn spring-boot:run # gRPC 9090, RSocket 7000, WebSocket ws://localhost:8080/quotes
```
Nothing to install: `protoc` and the gRPC codegen plugin resolve as Maven artifacts for your OS
and architecture, and all three servers start inside the test JVM.
## The operations
| | gRPC | RSocket route | WebSocket command |
|---|---|---|---|
| one quote | `QuoteService/GetQuote` | `quote` | `QUOTE <symbol>` |
| N quotes | `QuoteService/StreamQuotes` | `quotes` | `STREAM <symbol> <n> <delayMs>` |
| unbounded | `QuoteService/StreamUnbounded` | `quotes.unbounded` | `UNBOUNDED <symbol>` |
| same, `isReady()`-gated | `QuoteService/StreamUnboundedReady` | — | — |
| server-side produced count | `QuoteService/Produced` | `produced` | `PRODUCED` |
## Documentation
1. [The same use case, three ways](docs/01-three-protocols.md)
2. [How they measure — and why a loopback benchmark flatters the wrong one](docs/02-benchmarks.md)
3. [Payload: the one comparison that needs no benchmark](docs/03-payload.md)
4. [Back-pressure: the measurement that decides it](docs/04-backpressure.md)
5. [Choosing](docs/05-choosing.md)
## Captured output
| File | What it shows |
|---|---|
| [`payload-sizes.txt`](docs/output/payload-sizes.txt) | one message as protobuf, JSON and CBOR, in hex |
| [`request-response.txt`](docs/output/request-response.txt) | 5 000 sequential calls, p50/p99/mean per protocol |
| [`stream-throughput.txt`](docs/output/stream-throughput.txt) | 50 000 messages on one connection |
| [`backpressure.txt`](docs/output/backpressure.txt) | how many the server produced for a client that wanted 100 |
| [`grpc-isready.txt`](docs/output/grpc-isready.txt) | the documented gRPC fix, tested — and it did not work here |
| [`rsocket-codecs.txt`](docs/output/rsocket-codecs.txt) | what Boot configures for RSocket, and why CBOR wins |
| [`tests.txt`](docs/output/tests.txt) | 6 tests |
## Read the benchmark honestly
Client and server are the same process on one loopback interface, two cores. **There is no
network.** That deletes protobuf's size advantage, and it flatters whichever protocol does the
least work per message — which is why raw WebSocket wins both speed benchmarks and why that is
not the recommendation. Treat the ratios as indicative and the absolutes as meaningless off this
box.
The one result that *does* transfer is the back-pressure table, because it is a property of the
protocols rather than of the link.
## Four things this module exists to prove
1. **RSocket's `request(n)` is real and the others have no equivalent.** For a client that took
100 messages then went quiet for two seconds, the servers produced **100**, **1 346 233** and
**311 617** respectively.
2. **`ServerCallStreamObserver.isReady()` did not bound a gRPC producer here.** The documented fix
was tested; the transcript is committed as measured, including its instability.
3. **Raw WebSocket is the fastest and has the worst tail.** p50 of 124 µs against a p99 of
1 448 µs — a 12x spread, where gRPC's is 3.5x.
4. **CBOR keeps the field names, and it wins by list order.** It is binary JSON, not a schema
format: 67 bytes against protobuf's 35 and JSON's 83. Boot puts `JacksonCborEncoder` ahead of
`JacksonJsonEncoder`, and a bare `RSocketStrategies.create()` has neither.