Add the protocol-comparison module
This commit is contained in:
91
protocol-comparison/README.md
Normal file
91
protocol-comparison/README.md
Normal 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.
|
||||
Reference in New Issue
Block a user