Skip to content

Build1 publisher3 min readPublished

Batching frames outruns MessagePack on a 40-message-a-second telemetry socket

The encoding swap takes 40 to 55 percent off the bytes and adds a dependency. Setting binaryType and sending one frame every 50ms costs a line and a timer, and with deflate on the byte ranking can invert.

The Engineer · Build desk

Illustration accompanying Batching frames outruns MessagePack on a 40-message-a-second telemetry socket

What happened

  • A dev.to teardown of a high-frequency WebSocket telemetry channel ranks batching above the encoding change, on the grounds that batching costs nothing and usually wins more bytes and CPU back.
  • MessagePack is put at 40 to 55 percent smaller than JSON for telemetry-shaped objects with short keys and numeric values, while still encoding those key names as strings in each message.
  • Buffering samples and flushing on a 50 millisecond interval turns 40-plus sends a second into 20, without changing the encoder at all.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision The order of work inverts: one line of socket configuration and an interval timer land before @msgpack/msgpack enters package.json, which means the cheap fixes get measured on their own rather than credited to the new dependency.
  • constraint The extra 30 to 40 percent from positional arrays is paid for in a schema kept in sync by hand at both ends, so how often your message shape changes now decides which encoding you can afford.
  • contradiction With permessage-deflate enabled the byte ranking can reverse, so an adoption case resting on the compression figure alone is unproven until both configurations are measured on the actual payload.
  • capability Once one message carries several samples, the client can drain them into a ring buffer and let the next animation frame read the whole window instead of repainting per message.

Reading a Blob is the cost that hides. The browser socket's `binaryType` defaults to `blob`, and reading a Blob returns a promise, so a handler that looks synchronous is quietly scheduling a microtask per message [3]. No encoder fixes that. One assignment does, and it is worth grepping for in any codebase that already sends binary frames [2].

The byte case is real and smaller than it sounds. The teardown's sample payload, `{"cpu":18.4,"mem":152,"t":1699999999999}`, is about 46 bytes of JSON, and roughly half of that is field names resent on every message [8]. Call it 23 bytes of keys per sample [20]; at 40 messages a second that is about 920 bytes a second of repeated strings inside 1,840 bytes of payload [21]. Stack the binary encoding with a positional `[cpu, mem, t]` array and the 46-byte sample lands somewhere near 12 to 19 bytes [22]. That is worth having on the connection the article describes, which is fine on a laptop next to the server and falls apart over a phone [25]. The frame count is worth more [1].

Forty messages a second is forty frames, forty event-loop tasks on the server and forty `onmessage` callbacks in the browser, each frame carrying framing overhead [11][10]. A 50ms timer puts two samples in each frame [24]. The teardown also floats 16ms for a UI that paints at 60fps anyway [13]. Check the arithmetic on that one: 1000/16 is 62.5 sends a second, above the producer's 40, so on a steady 40/s stream the 16ms timer batches nothing and only earns its keep when the producer bursts [23].

The figure I would not carry into my own service is the 40 to 55 percent, and not because it is wrong. It is stated for telemetry-shaped objects, short keys and numeric values [4], and MessagePack still writes `cpu`, `mem` and `t` as strings in every message [5]. Deflate attacks exactly that redundancy: `permessage-deflate` squeezes repetitive JSON well because the repeated keys compress away, while binary output gives the compressor less to find, so binary plus compression is sometimes larger than compressed JSON [7]. For the size claim to transfer you need short keys, numeric values, and deflate off on that connection. The teardown says to measure both configurations and publishes no byte counts for the comparison [6].

Then there is the part you pay for after shipping. Binary framing means you can no longer read your own traffic in DevTools [17]. The suggested hatches are a `?format=json` query parameter that flips the server back to plain JSON for development, and logging decoded frames behind a client flag rather than reading the wire [18]. The client gate is `process.env.NODE_ENV === "production"` [19], which means the serialiser you debug against is not the serialiser you ship, and the binary path is the one no human ever eyeballs.

What to watch

  • Published byte counts for deflated JSON against MessagePack on the same payload, which the teardown recommends comparing but does not measure.
  • Whether the 40 to 55 percent holds for long key names or nested objects, since the figure is given only for short-key numeric telemetry.
  • Decode CPU numbers on low-end phones, given that parse and serialise time is named as a separate cost but only bytes and frame counts are quantified.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories