My last post was about how to use Redis with WebSockets to create a dynamic front end application with long running background processes. The example used a single node WebSocket server, WebSockets are stateful though, meaning it doesn't scale without some work. This article demonstrates a horizontally scalable WebSocket implementation with a Redis backend you can deploy anywhere containers run.
Prerequisites
- An understanding of how to use Node.js
- You need a Redis server, redis.io
- You need a WebSocket server and a front end app, I wrote an article about setting up WebSockets With SvelteKit
- An understanding of Socket.IO
- Basic understanding of horizontal scaling concept in Kubernetes
If you'd like to read through the previous article I mentioned above you can read that here: Simply Redis and WebSockets.
Note: I have a bias towards using containers in Kubernetes, so I'm going to discuss horizontal scaling using Kubernetes as a point of reference.
The Problem
WebSockets are stateful and basically that means one WebSocket server maintains connections with a front end application separately. In Kubernetes (and scaling systems in general) we have a concept called horizontal scaling, in practice you run multiple containers with the same runtime. Scaling helps meet demand as usage increases, i.e. more people using your web application. So in the Kubernetes deployment for your WebSocket server you might set the number of replicas (here's an example in Kubernetes docs) to two in production and now you have two separate WebSocket servers running that can receive connections.
This is the problem. The WebSocket in container one receives a connection from the client and by default Kubernetes won't know to keep the connection persistent. Internally the WebSocket connection is load balanced to the other container at some point and the front end starts erroring out.
The Solution
There are a few ways to solve this problem and one solution involves using Redis as a backend to sync the replicas of your WebSocket server. Socket.IO has a handy Redis adapter feature that does this so it doesn't matter which instance of the WebSocket server you connect to. Each one knows about your front end.
Step 1 - Install Dependencies
Install redis, Socket.IO and the Redis adapter:
Step 2 - Sync the WebSocket Servers
Now we need to create the connection to Redis for WebSockets using the adapter so your servers will be sync'd:
Note: This block of code takes an HTTP server object and creates a WebSocket server along side the connection so the front end can connect using the WebSocket protocol. This block of code borrows from the pattern described in my article WebSockets with SvelteKit and should be easily conformed to how you run your servers.
Addendum - Sticky Sessions
If you are using HTTP long polling in your client you will need to enable sticky sessions, in Kubernetes this is called session affinity. More information can be found here in Socket.IO's documentation. But it may not be necessary very often, seeing as WebSockets are widely supported.
Conclusion
And that's how you can horizontally scale WebSockets with Redis, we talked about why you run into scaling problems with WebSockets, one viable solution with Redis, and a bit about session affinity in Kubernetes. Happy scaling! 📈