blue_tides: the Go server built to see where WebSockets break

It reads fixed-size frames from disk, pushes them through per-client egress channels, and logs the latency of every hop.

8 min read • View on GitHub • More from dashn9

A wide machine-like scene where a strip of film frames moves through a calibrated test rig with gauges, branching channels, and a stopwatch. It explains that blue_tides is about measuring how data flows and where the pipeline slows down.
The whole point is not to stream prettier. It is to make every hop measurable.
Key Takeaways

This server is built to make pressure visible

The repo, blue_tides, is not trying to behave like a polished media stack. It is trying to answer a narrower question: what happens when a Go server has to move the same frame through disk, channels, and sockets without hiding the cost?

That is why the interesting part is not the fact that it streams. It is the way it exposes the handoff points. Every boundary, from file read to callback to WebSocket write, is a place where latency and backpressure can show up.

Why the code feels like a lab setup

The repository layout reads like a test bench. `server` and `server/websocket` handle connection management, `handler` owns the file and movie logic, `database` sits off to the side as a persistence wrapper, and `blue_tides.go` ties the whole experiment together.

That split matters because it keeps the streaming path legible. The code is not built around a product surface. It is built around one narrow pipeline, and that makes the tradeoffs easy to see.

func (m *Movie) PlayMovie(meta MovieMeta, send func([]byte), logFrame func(time.Duration)) {\n    last := time.Now()\n    m.ReadFile(int(meta.Scale), func(chunk []byte) {\n        logFrame(time.Since(last))\n        send(chunk)\n        last = time.Now()\n    })\n}\n\nfunc (c *Client) writeMessages() {\n    for {\n        select {\n        case msg, ok := <-c.egress:\n            if !ok {\n                return\n            }\n            if err := c.Conn.WriteMessage(websocket.BinaryMessage, msg); err != nil {\n                return\n            }\n        case <-c.done:\n            return\n        }\n    }\n}

The strange, elegant bridge between files and sockets

The sharpest idea in the repo is the callback-to-channel bridge. The file reader does not know about sockets. It just reads fixed-size chunks and hands each one to a function, which lets the rest of the system decide whether to measure, queue, or forward that chunk.

That separation is small, but it is the difference between a stream you can inspect and a stream that disappears into abstraction. Once the callback exists, timing can become a sidecar concern instead of being buried inside the network code.

The key idea is not just moving bytes. It is splitting the data path from the measurement path, then reconnecting them at one writer per socket.

One goroutine writes, and everyone else gets out of the way

The WebSocket side follows a classic Go rule, but the repo makes it concrete. Each client gets its own egress channel, the manager registers and unregisters sockets, and a dedicated write loop owns the actual socket writes.

That design does two jobs at once. It avoids concurrent write bugs on the same connection, and it turns congestion into something visible. If a client slows down, the queue is where you see it.

type Manager struct {\n    ClientList map[*Client]bool\n    Lock       sync.RWMutex\n}\n\nfunc (m *Manager) ServeWs(w http.ResponseWriter, r *http.Request) {\n    conn, _ := upgrader.Upgrade(w, r, nil)\n    client := &Client{Conn: conn, egress: make(chan []byte, 32)}\n\n    m.Lock.Lock()\n    m.ClientList[client] = true\n    m.Lock.Unlock()\n\n    go client.readMessages()\n    go client.writeMessages()\n}

The important part is not just that the code is concurrent. It is that the concurrency is disciplined. One goroutine owns the writer, channels absorb the traffic, and the manager keeps the client roster coherent.

The timestamp log is the real product

The sidecar timestamp log turns the repo from a stream demo into a measurement tool. Every frame can be timed as it moves, which means the project is interested in latency, not only throughput.

A tight close-up of a single file chunk sliding into a narrow channel beside a handwritten timing log and a pocket watch. It explains that blue_tides measures each frame hop, so latency is treated as a first-class output rather than an afterthought.
A streaming path is only useful here if it leaves a timing trail behind it.

That is why the repo feels more like a harness than an app. A throughput number can flatter a system. A per-frame log shows where the pipeline is actually paying for time.

What blue_tides is, and what it is not

The easiest mistake is to compare it to a generic broadcast server or a production media streamer. That misses the point. blue_tides is closer to a stress rig with a streaming shape, built to make the cost of each hop legible.

DimensionGeneric WebSocket broadcast serverblue_tides
Data sourceArbitrary messages from handlers or pub-subFixed-size chunks read from disk with a frame scale
Write modelOften many paths can try to writeOne writer goroutine per socket
BackpressureUsually hidden inside shared broadcast logicVisible in per-client egress channels
Latency visibilityThroughput first, timing secondPer-frame timing log is built in
Primary useChat, notifications, live updatesStress test and measurement rig
Failure modeRace conditions or tangled locksQueue growth and slower frame delivery

That distinction explains the whole design. The fixed chunk size, the single-writer socket path, and the timing log all point to the same goal: reveal pressure before it becomes a mystery.