Build1 distinct publisher3 min readUpdated
The author argues Go brokers stall on garbage collection, not sockets. The fix he describes removes allocation from the consumer path and leaves it sitting on the producer path.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
A Go developer has published a writeup of HermitMQ, a message broker he says reaches a throughput of a million messages per second, and his diagnosis is worth more than his number: the wall in Go is the garbage collector, not the socket [s1c1][s1c2]. That matters because the remedies he describes are cheap and portable to anything you are already running: a fixed binary header instead of JSON, pooled buffers, and reads served straight from file to socket [s1c3].
The argument is that once you are past a few hundred thousand messages per second, parsing each one with standard JSON allocates a large number of small objects, the collector wakes too often, and you pay for it in CPU time and latency spikes [s1c2]. So he dropped standard serialization entirely and packs every message into a header of exactly 29 bytes: one magic byte, eight bytes of nanosecond timestamp, eight bytes of offset, four each for key size and payload size, and four for a record count to support batching [s1c4][s1c5]. Those field widths add up to the stated 29 [s1c6]. The magic byte is used for version checking and for discarding bad packets immediately, and the broker writes the offset itself so ordering is its property rather than the client's [s1c7][s1c8]. Reading is done with the binary package, with buffers reused through sync.Pool, which he says yields practically zero allocation under standard loads [s1c9].
The useful part of the post is that he does not stop at that sentence. He states plainly that the allocation win is one-sided: consumer delivery is clean, but the decode path from producers still calls make on a slice sized to the payload for every message [s1c10][s1c11]. At the headline rate, that is on the order of a million heap allocations per second on the ingest side [s1c12]. The GC has been moved off half the data path, not removed from it.
Delivery is where he claims the bigger effect. On a client request the broker locates the offset in the log file, wraps it in a section reader, and hands it to io.CopyN with the connection as the destination, so the bytes go from the file to the consumer socket without being read into program memory first [s1c13][s1c14]. That is asserted rather than demonstrated in the material supplied here; there is no syscall trace, and the promised benchmark numbers do not appear in the portion of the text available to me [s1c15]. Anyone adopting the pattern should confirm the copy actually takes the kernel fast path with their Go version and their file and connection types, because the whole claim collapses into an ordinary buffered copy if it does not.
The storage design is conventional in a good way. Everything lands in a write-ahead log split into segments, each with a .wal file of raw data and an .idx file mapping offsets to physical bytes, with the indexes mmapped so lookups are a binary search in memory [s1c16][s1c17]. Background compaction deduplicates by key [s1c18]. On startup the broker scans the WAL, and if a header claims more bytes than the file physically contains it treats the write as interrupted and truncates the torn tail with os.Truncate rather than failing the segment [s1c19][s1c20].
Two things to watch. First, whether the planned move to zero-serialization access, reading byte offsets directly in the network buffer without unpacking into Go structs, actually ships, since that is what would close the producer-side allocation gap [s1c21]. Second, whether benchmark methodology follows the code, which is on GitHub [s1c22]: batch size, fsync policy, and whether producer and consumer ran on the same machine decide what the million means. Header overhead alone is 29 MB/s of non-payload bytes at that rate [s1c23].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The broker reads the stream using the binary package and reuses buffers via sync.Pool; the author says the result is practically zero memory allocation under standard loads.
The author states that an absolute victory over allocations and the GC has been achieved on the side of serving data to consumers, thanks to io.CopyN.
When a client requests data, the broker looks up the offset in the log file and, instead of reading the file into program memory, computes bytesToSend as HeaderSize plus keySize plus payloadSize, builds an io.NewSectionReader over the WAL file at that position, and calls io.CopyN(conn, reader, bytesToSend).
The author states that data flows straight from the physical file on disk into the consumer network socket, keeping latencies minimal and avoiding CPU work moving bytes, because copying from kernel to user space and back is double the work for CPU and RAM.
According to the author, when the message counter exceeds hundreds of thousands per second the main problem for a Go developer is the garbage collector: parsing every message via standard JSON allocates a massive number of small objects, the GC wakes too frequently, eats CPU time and causes network latency spikes.
The stated main features are ditching heavy wrappers like JSON in favour of a custom 29 byte binary protocol, and using a direct file to socket copy mechanism for network transmission.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Self-reported build log with code, no measurements
The supplied material is a single first-person post by the project's own author. Structural claims are backed by real code excerpts (Message struct, io.CopyN path, os.Truncate recovery, shard hash), which is why this is not near zero. But the load-bearing performance claim has no benchmark harness, hardware, message size, latency distribution, allocation profile or syscall trace, and nothing in the cluster is independently corroborated.
Public repo, no observed users
The only adoption fact in the cluster is that the code exists publicly on GitHub alongside an announcement post. No stars, forks, contributors, downloads, production deployments or third-party integrations are disclosed anywhere in the supplied material.
Headline rate outruns the shown evidence
A million-messages-per-second headline plus phrases like 'absolute victory over allocations and the GC' and 'practically zero memory allocation' sit alongside the author's own admission that the producer path allocates a payload slice per message, which at that rate is roughly a million allocations per second on the very path the post blames for GC stalls. With no benchmark, profile or syscall evidence supplied, the framing is meaningfully overstated relative to what is shown, though the author's candour about the remaining compromise keeps the gap short of extreme.
Author promoting his own project
The single source is written by HermitMQ's creator, links his own repository, frames the work with superlatives, and previews future releases — a clear promotional interest in the throughput claim landing. This is disclosed openly rather than hidden, and the post volunteers an unflattering limitation, which moderates the score.
Descriptive claims solid, performance claim unresolved
Confidence in what the project does is high because the source shows its own code, and one derived arithmetic check (field widths summing to 29 bytes) validates internally. Confidence in whether the headline performance holds is low: one self-interested publisher, no measurements, no independent replication, and no adoption signal to triangulate against.
build
One webrtc.NewAPI() per SDP offer is a load-bearing bug, and thirty viewers will find it1 distinct publisher
build
Your meter now runs on someone else's machine: signed receipts, fsync, and failing open1 distinct publisher
build
A Timed-Out Reset SMS Is Not A Failed One, And Your Retry Code Probably Disagrees1 distinct publisher
build
Buy transactional email on recovery controls, not send price1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 18, 2026