[HN Gopher] The Hidden Complexity of Scaling WebSockets
___________________________________________________________________
The Hidden Complexity of Scaling WebSockets
Author : atul-jalan
Score : 34 points
Date : 2025-01-24 19:48 UTC (3 hours ago)
(HTM) web link (composehq.com)
(TXT) w3m dump (composehq.com)
| peteforde wrote:
| This is all true, but it also serves to remind us that Rails
| gives developers so much out of the box, even if you're not aware
| of it.
|
| ActionCable is Rails' WebSockets wrapper library, and it
| addresses basically every pain point in the post. However, it
| does so in a way that all Rails developers are using the same
| battle-tested solution. There's no need for every project to hack
| together its own proprietary approach.
|
| Thundering herds, heartbeat monitoring are both covered.
|
| If you need a messaging schema, I strongly recommend that you
| check out CableReady. It's a powerful library for triggering
| outcomes on the client. It ships with a large set of operations,
| but adding custom operations is trivial.
|
| https://cableready.stimulusreflex.com/hello-world/
|
| While both ActionCable and CableReady are Rails libraries, other
| frameworks would score huge wins if they adopted their client
| libraries.
| atul-jalan wrote:
| Node has similar libraries like Socket.IO too, but it over-
| abstracts it a bit in my opinion.
| hombre_fatal wrote:
| I've done my share of building websocket servers from
| scratch, but when you don't use libraries like ActiveCable or
| socket.io, you have to build your own MessageID
| reconciliation so that you can have request/response cycles.
| Which is generally what you want (or eventually want) in a
| websocket-heavy application.
| send(payload).then(reply => ...)
| atul-jalan wrote:
| Yep, for our application, we have an `executionId` that is
| sent in essentially every single WebSocket message.
|
| But client and server use it to maintain a record of
| events.
| exabrial wrote:
| I recall another complication with websockets: IIRC it's with
| proxy load balancers, like binding a connection to a single
| connection server, even if the backend connection is using
| HTTP/2. I probably have the details wrong. I'm sure someone will
| correct my statement.
| hpx7 wrote:
| Horizontal scaling is certainly a challenge. With traditional
| load balancers, you don't control which instance your clients
| get routed to, so you end up needing to use message brokers or
| stateful routing to ensure message broadcasts work correctly
| with multiple websocket server instances.
| atul-jalan wrote:
| I think there is a way to do it, but it likely involves custom
| headers on the initial connection that the load balancer can
| read to route to the correct origin server.
|
| I imagine the way it might go is that the client would first
| send an HTTP request to an endpoint that returns routing
| instructions, and then use that in the custom headers it sends
| when initiating the WebSocket connection.
|
| Haven't tried this myself though.
| arccy wrote:
| I think it's more that WebSockets are held open for a long
| time, so if you're not careful, you can get "hot" backends with
| a lot of connections that you can't shift to a different
| instance. It can also be harder to rotate backends since you
| know you are disrupting a large number of active clients.
| 10000truths wrote:
| The key to managing this complexity is to avoid mixing transport-
| level state with application-level state. The same approach for
| scaling HTTP requests also works for scaling WebSocket
| connections:
|
| * Read, write and track all application-level state in a
| persistent data store.
|
| * Identify sessions with a session token so that application-
| level sessions can span multiple WebSocket connections.
|
| It's a lot easier to do this if your application-level protocol
| consists of a single discrete request and response (a la RPC).
| But you can also handle unidirectional/bidirectional streaming,
| as long as the stream states are tracked in your data store and
| on the client side.
___________________________________________________________________
(page generated 2025-01-24 23:00 UTC)