21 — Realtime Communication — WebSockets, Melody, and Centrifugo
Chat apps, live dashboards, collaborative tools — anything where the server pushes updates without being asked. The model that clicked: Go's concurrency model — one cheap goroutine per connection, with channels coordinating them — makes it an unusually good fit for realtime, and the ecosystem offers three layers: raw WebSockets for full control, Melody as a minimalist wrapper that handles the boilerplate, and Centrifugo as a full-featured messaging server when you need presence, history, and scale. [1] The reason Go shines here is structural: a server holding 50,000 open WebSocket connections is holding 50,000 goroutines, which Go handles comfortably where a thread-per-connection model would collapse.
Why Go fits realtime
Realtime servers are fundamentally about many long-lived concurrent connections. Every connected client is an open socket that the server must read from, write to, and coordinate with all the others — broadcasting a chat message to everyone in a room, pushing a price update to every subscriber of a stock. Two Go properties make this cheap [1]:
- Goroutine-per-connection. Each connected client gets its own goroutine reading its socket, costing kilobytes rather than megabytes. 50,000 connected clients is a normal load for a Go realtime server on modest hardware.
- Channels for coordination. Broadcasting a message to all connections is a fan-out pattern (covered in the concurrency notes) — send the message to a channel that every connection's goroutine reads from. The coordination primitive is built in.
The result is that realtime in Go is not an exotic specialty; it is the concurrency model applied to long-lived connections.
WebSockets — the transport
WebSockets are the standard transport for bidirectional realtime in the browser — a single upgraded HTTP connection that stays open and allows both client and server to push frames at any time [2]. Unlike HTTP request/response, a WebSocket is persistent and either side can initiate a message. This is what powers chat, multiplayer cursors, and live trading UIs.
Go's standard library does not include a WebSocket package (it predates the WebSocket standard's stability), so the ecosystem standard is gorilla/websocket or the newer nhooyr.io/websocket (now coder/websocket). Both expose the same core loop: upgrade the HTTP connection, then read and write frames in a per-connection goroutine. The server fans out messages by ranging over all live connections and writing to each.
The lower-level alternative is Server-Sent Events (SSE) — a one-way server-to-client stream over plain HTTP. SSE is simpler (no protocol upgrade, works through more proxies) and is enough for dashboards and notification feeds where the client only needs to _receive_ pushes. WebSockets are the choice when the client also needs to send frequent messages upstream.
Melody — the minimalist wrapper
Writing raw WebSocket code gets repetitive fast: upgrade the connection, register it in a connection set, broadcast on each message, handle disconnects and pings. Melody is a minimalist framework that wraps this pattern with a clean API [3]:
m := melody.New()
m.HandleConnect(func(s *melody.Session) { /* a client joined */ })
m.HandleDisconnect(func(s *melody.Session) { /* a client left */ })
m.HandleMessage(func(s *melody.Session, msg []byte) {
m.Broadcast(msg) // send to every connected session
})Melody handles session management, ping/pong keepalive, message broadcasting, and rooms (session groups). It integrates cleanly with an existing net/http or framework-based server. For a chat app, a notification broadcaster, or any realtime feature where you want the structure without writing the plumbing, Melody is the productive middle layer — more than raw WebSockets, far less than a full messaging server.
Centrifugo — the messaging server
When the realtime needs outgrow what a library can provide — presence (who is online), message history (replay missed messages), per-channel permissions, horizontal scaling across multiple server nodes, and multi-protocol clients (WebSocket, SSE, HTTP streaming) — the answer is a dedicated messaging server. Centrifugo is the Go-native option [4]:
- Channels and subscriptions — clients subscribe to named channels; the server routes published messages to all subscribers.
- Presence and history — the server tracks who is in a channel and retains recent messages so reconnecting clients can catch up.
- Scalability — Centrifugo can be scaled horizontally with Redis as a backing store, so multiple server nodes share the pub/sub load.
- Protocol flexibility — the same backend speaks WebSocket, SSE, and HTTP streaming, so clients pick the transport that works for them.
The architecture is that your Go application publishes events to Centrifugo (over its API or a Redis-backed broker), and Centrifugo manages the fan-out to all connected clients. This separates the realtime fan-out concern from your application logic — your service does not hold the client connections at all, Centrifugo does. For a system where realtime is central and the connection count is large, Centrifugo (or a similar server like NATS) is what lets the application servers stay stateless.
How I use this
The choice maps to scale and feature needs. For a single feature with a few hundred connections — a live notification bell, a document-coordination cursor — Melody on top of the existing HTTP server is the sweet spot; it adds the broadcast plumbing without a new infrastructure component. For raw control or an unusual protocol, the underlying WebSocket libraries are fine to use directly. Centrifugo enters the picture when the realtime layer is genuinely a product surface — a chat platform, a live-trading dashboard — where presence, history, and multi-node scale are requirements, not nice-to-haves. In every case the underlying reason Go handles it well is the same: a goroutine per connection, a channel to coordinate, and the whole fan-out is just the concurrency patterns from the earlier notes applied to long-lived sockets. That uniformity is why a Go backend is often the default recommendation for realtime.
References
[1] The Go Authors, "net/http package," pkg.go.dev, 2024. [Online]. Available: https://pkg.go.dev/net/http
[2] Wisemonks, "Implementing WebSockets in Golang," Medium, 2024. [Online]. Available: https://medium.com/wisemonks/implementing-websockets-in-golang-d3e8e219733b
[3] O. Hong, "olahol/melody: Minimalist websocket framework," GitHub, 2024. [Online]. Available: https://github.com/olahol/melody
[4] Centrifugal, "Centrifugo," centrifugal.dev, 2024. [Online]. Available: https://centrifugal.dev/
Knowledge check · Question 1 of 5
Why is Go well-suited for realtime servers holding tens of thousands of connections?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!