The ingest path started life as JSON.parse in an onmessage handler, like everyone’s does.
At forty thousand messages a second that is forty thousand strings decoded, forty thousand
object graphs allocated, and a garbage collector doing steady-state demolition work behind the
UI. The profiler said rendering was fine. The frames said otherwise — parsing was the tax
every frame paid before drawing anything.
The first move was binary frames. The telemetry schema is fixed, so a message became a plain
struct layout: known offsets, little-endian, no field names on the wire. A DataView over the
received ArrayBuffer reads any field in place. Nothing is deserialized, because there is
nothing to deserialize into — the buffer IS the data.
The second move was ownership. Each socket writes into a preallocated ring buffer sized for several seconds of burst; a message is a byte range in that ring, described by an offset and a length, never by a copy. Consumers get views. The renderer reads the fields it draws and ignores the rest. When the write head laps a range, that range is simply old — which is the contract: hold a view past one lap and it is your bug, and it shows up in soak tests, not in production.
“Zero-copy” is, strictly, one copy short of the truth — the browser copies the network payload
into the ArrayBuffer it hands you, and that copy is not yours to remove. What you can remove
is every copy after it, and those were the ones the collector was billing us for.
The chart in FIG.01 is the whole argument: ingest cost per message dropped by an order of magnitude, and the P99 frame time followed it down without the renderer changing at all. The fastest parser is the one that never runs.
