Add the sse-websocket module
This commit is contained in:
80
sse-websocket/docs/06-choosing.md
Normal file
80
sse-websocket/docs/06-choosing.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# 6. Choosing, and when the answer is neither
|
||||
|
||||
Previous: [5. The limits](05-limits.md)
|
||||
|
||||
---
|
||||
|
||||
## The decision, in one table
|
||||
|
||||
| If | Use | Because |
|
||||
|---|---|---|
|
||||
| Server pushes, client never speaks | **SSE** | A return type. No broker, no converter stack, no origin policy. Reconnect is free. |
|
||||
| Clients talk to each other, or to the server, often | **STOMP over WebSocket** | Routing, presence, per-user destinations, and a path to a real broker without touching controllers. |
|
||||
| One-off progress on an operation the client already started | **SSE** | The stream is scoped to the request. Nothing to clean up. |
|
||||
| Payloads are binary, or large | **WebSocket** | SSE is line-oriented UTF-8; binary costs a third in base64. |
|
||||
| The consumer is another service, not a browser | **neither** — see below | Browsers are the only reason to accept SSE's or STOMP's constraints. |
|
||||
| You need the consumer to say "slow down" | **neither** | Covered in the next article. |
|
||||
|
||||
## The costs nobody puts in the table
|
||||
|
||||
**SSE costs a connection per stream, per tab.** Over HTTP/1.1 a browser allows about six
|
||||
concurrent connections per origin, and an open `EventSource` is one of them. Three tabs of your
|
||||
dashboard leaves three for everything else on the page. Over HTTP/2 they multiplex over one
|
||||
connection and the limit effectively disappears — so "are we behind HTTP/2 end to end,
|
||||
including the proxy?" is a real input to this decision, not a detail.
|
||||
|
||||
**SSE costs one serialisation per subscriber per event.** The fan-out in
|
||||
[`EmitterRegistry.broadcast`](../src/main/java/com/ankurm/ssews/EmitterRegistry.java) is a loop:
|
||||
a thousand open dashboards means a thousand `send()` calls on one scheduler thread, every tick.
|
||||
The obvious fix — serialise once and write the bytes N times — is not something
|
||||
`SseEmitter` offers.
|
||||
|
||||
**SSE does not pin threads, though.** That objection is wrong, and
|
||||
[`sse-concurrency.txt`](output/sse-concurrency.txt) is the evidence:
|
||||
|
||||
```
|
||||
=== server.tomcat.threads.max=10, clients=40 ===
|
||||
emitters open : 40
|
||||
connector pool size : 10 (max 10)
|
||||
connector active : 0
|
||||
one broadcast reached: 40 streams
|
||||
clients that read it : 40
|
||||
```
|
||||
|
||||
**STOMP costs a stateful server.** Sessions live in one JVM's memory. Two instances behind a load
|
||||
balancer do not share subscriptions, so a message published on instance A never reaches a
|
||||
subscriber on instance B — which is the real reason to move to a broker relay, well before
|
||||
you need persistence.
|
||||
|
||||
## Should you build this at all?
|
||||
|
||||
Three cases where the honest answer is no:
|
||||
|
||||
- **Updates arrive every few minutes.** Polling every thirty seconds is one endpoint, no
|
||||
lifecycle, no proxy configuration, no reconnect logic and no leaked emitters. The right answer
|
||||
more often than it is chosen.
|
||||
- **You want a chat and you have one server and no plan for a second.** Everything in
|
||||
[chapter 4](04-stomp.md) is real work, and the first time you scale out you will be doing it
|
||||
again with a broker relay. Decide about the broker first.
|
||||
- **The client is another service.** SSE's advantage is that browsers implement it. STOMP's
|
||||
advantage is that browsers can speak it. Neither advantage applies service-to-service, and
|
||||
neither protocol gives you flow control, deadlines or a schema — which is what the
|
||||
companion article on RSocket, gRPC and raw WebSocket is about.
|
||||
|
||||
## Everything else, one line each
|
||||
|
||||
- **Behind nginx**, a chunked SSE response is buffered by default and the client sees nothing
|
||||
until the buffer fills. `proxy_buffering off` or the `X-Accel-Buffering: no` response header.
|
||||
- **`EventSource` cannot send an `Authorization` header.** Cookie, or a query parameter you then
|
||||
have to keep out of access logs.
|
||||
- **Heartbeats.** SSE: send a `:` comment every N seconds. STOMP: `setHeartbeatValue` plus a
|
||||
`TaskScheduler`, and both sides must agree.
|
||||
- **Compression.** `text/event-stream` compresses extremely well and Boot's HTTP compression is
|
||||
off by default; `server.compression.mime-types` must list it explicitly.
|
||||
- **Surefire does not discover static nested test classes.** Seven of this module's seventeen
|
||||
tests silently did not run until they were split into top-level classes. Check the count, not
|
||||
the colour.
|
||||
|
||||
---
|
||||
|
||||
Previous: [5. The limits](05-limits.md)
|
||||
Reference in New Issue
Block a user