Skip to main content

Why I Built Another WebSocket Library for Go (and Why It Might Be the Last One You Need)

· 7 min read
Author of wshub

"Another WebSocket library" is a fair thing to be skeptical of. Go already has several good ones, some of them excellent at exactly what they set out to do. So before explaining why wshub exists, the honest version of this post has to start somewhere else: what's actually already out there, and what each one leaves for you to build yourself.

Building Backpressure into a Go WebSocket Server

· 9 min read
Author of wshub

Every WebSocket server eventually gets a slow client — bad wifi, a suspended mobile app, a browser tab that stopped running its event loop. The server keeps producing messages for it regardless. What happens to those messages is a design decision, and "block until the client catches up" is the wrong one: block the wrong goroutine and you've turned one slow client into an outage for every other connection on the process.

Graceful WebSocket Shutdown in Go

· 8 min read
Author of wshub

Article one in this series showed the two-phase pattern — Drain then Shutdown — as the answer to "what happens to 8,000 open connections when a deploy kills the pod." This one is about why it needs to be two phases with real machinery behind each, not a sync.WaitGroup.Wait() and a signal handler. The naive version — stop accepting connections, wait for every existing one to close on its own — sounds graceful and has a hole in it big enough to hang your rollout: a client that's connected but silent, sending and receiving nothing, never triggers a natural disconnect. Wait for it to leave on its own and you're not draining gracefully, you're waiting for Kubernetes' own termination grace period to expire and SIGKILL you anyway — which is exactly the outcome graceful shutdown exists to avoid.

How to Handle 10,000 WebSocket Connections in Go

· 10 min read
Author of wshub

"Can it handle 10,000 connections?" gets asked about every realtime service, usually as a stand-in for "is this production-grade." It's the wrong question in one specific way: for a Go server, holding 10,000 idle-ish WebSocket connections open was never the hard part. The C10K problem — the one that gave the number its mythology — was about thread-per-connection servers where each connection cost an OS thread and a context switch. Go's runtime solved that structurally before you wrote a line of your handler. What's left is arithmetic: how much memory each connection actually costs, and which OS limits you'll hit before your own code does.

Building a Production-Grade WebSocket Server in Go

· 11 min read
Author of wshub

Every Go WebSocket server starts the same way. You pull in gorilla/websocket or nhooyr.io/websocket, write an Upgrade call, spin up a goroutine that reads frames in a loop, and it works. You broadcast a message by iterating a map[*Client]bool and calling conn.WriteMessage on each one. Twenty lines, a working demo, and a reasonable belief that the hard part is behind you.

It isn't. The hard part starts at the traffic your demo never saw: a client that stops reading, a deploy that needs to happen without dropping connections, a second replica that has no idea the first one exists. None of that is in the WebSocket RFC, and none of it is optional once real users are on the other end of the socket.

Scaling WebSockets Across Multiple Go Servers with Redis

· 7 min read
Author of wshub

A stateless HTTP handler scales the boring way: put a load balancer in front, add replicas, done — any instance can answer any request because no instance holds anything the others need. A WebSocket server breaks that model by definition. The connection is the state. If Alice is connected to node B and your billing webhook fires on node A and calls hub.SendToUser("alice", ...), node A's hub has no idea Alice exists. The message doesn't error, doesn't queue, doesn't retry — it just isn't delivered, because from node A's point of view there's no client called Alice.