Concluded·3 Aug 2026 · 5 min read
A Unified Namespace built from parts, to find out which part is the product
Mosquitto 2 · MQTT · JSON Schema (Ajv) · Node.js 20 · Docker Compose · Proxmox LXC
The common view is that a Unified Namespace is an MQTT broker with a sensible topic structure, and that the products sold around it are therefore optional. I mostly believed that, so I built one from primitives.
Building it was easy. Finding out what was on it was not.
The two agreements
A namespace is two conventions and nothing else.
The address. northwind/<site>/<area>/<line>/<cell>/<tag> — ISA-95 shaped,
so a topic states where in the plant a reading comes from.
The payload. Every message must carry timestamp_ms, value, unit and a
quality of good, uncertain or bad.
THE TWO AGREEMENTS THAT MAKE A NAMESPACE
Both live in sim/contract.js, thirty-odd lines including the topic regex. That
file is the entire difference between a namespace and a broker with tidy names —
provided something enforces it.
The estate
| Sites | rotterdam, houston, seville |
| Structure | 3 sites × 2 areas × 2 lines × 2 cells × 6 tags = 144 tags |
| Cadence | 48 fast (1 s) · 48 slow (30 s) · 48 event-driven (~10 min) |
| Run | 186 s, 9,120 messages |
| Broker memory | 2.36 MiB |
The cadence mix is the load-bearing design choice. A generator where every tag publishes at the same rate would have made this lab produce a completely different and much less true answer.
Experiment 1 — discovery by listening
A consumer subscribed to northwind/# and tried to rebuild the tag tree from
traffic, which is the only mechanism a bare broker offers.
| Listening for | Tags found | of 144 |
|---|---|---|
| 10 s | 48 | 33.3% |
| 30 s | 48 | 33.3% |
| 60 s | 95 | 66.0% |
| 120 s | 96 | 66.7% |
| 180 s | 96 | 66.7% |
Discovery time for the ones that did appear: p50 33 s, p95 33 s, max 63 s.
TAGS DISCOVERED BY LISTENING
Forty-eight tags were never seen. Correctly configured, correctly named, on
the right topics — and silent, because they are the event-driven ones:
filter_blocked, estop_tripped.
WHAT A LISTENING CONSUMER KNEW ABOUT
Pressure, flow, temperatures, run hours. Anything on a scan class shows up within a minute simply by publishing.
filter_blocked, estop_tripped. Configured, correct, on the naming convention, and completely invisible until the day they fire.
A broker holds no directory. A tag exists, as far as any consumer can tell, only once it has published. Every "browse the namespace" screen you have used is something built on top that keeps its own catalogue.
The consequence is not a slightly incomplete picture, it is a biased one. The tags you cannot discover are precisely the rare, important ones. A dashboard assembled by discovery shows every pressure in the plant and no emergency stops, and looks complete while doing it.
Nor can listening distinguish a tag that is quiet from a tag that does not exist, or from one that died last Tuesday — which is the same staleness-versus-liveness problem that showed up in the UMH lab from the other direction.
Experiment 2 — what enforces the contract
About 2.6% of publishes carried a defect, chosen to be the kind that happens on real projects rather than obviously silly ones.
| Defect | Count |
|---|---|
missing unit | 65 |
| bare number, no envelope | 50 |
quality: "OK" instead of good | 48 |
| timestamp in seconds, not milliseconds | 44 |
| topic with capitals and a space | 32 |
| total | 239 |
| Result | |
|---|---|
| Accepted by the broker | 239 of 239 (100%) |
| Rejected by the validator | 239 of 239 (100%) |
239 DELIBERATELY BROKEN MESSAGES
The broker delivered all of it, faithfully and instantly. It has no opinion about the contract, which is correct behaviour for a broker and a serious problem for anyone who assumed the namespace was protecting them.
The validator caught everything — but only because it exists. Forty lines of Ajv against the schema. Delete them and the malformed data flows on, looking exactly like good data.
The timestamp defect deserves attention: forty-four readings arrived stamped in 1970. Nothing rejected them. In a historian they either vanish into an ancient partition or silently skew every aggregate that touches them, and the discovery lag on that kind of fault is measured in months.
What could and could not be built
| Part | Cost | Result |
|---|---|---|
| a broker | minutes | ✓ 2.36 MiB for 144 tags |
| an ISA-95 naming convention | free | ✓ it is a document |
| a payload contract | ~30 lines | ✓ JSON Schema |
| enforcement of it | ~40 lines | ✓ 239/239 caught |
| a directory of what exists | not possible by listening | ✕ 33% never found |
BUILDING ONE FROM PARTS
Which is the honest answer to buy-versus-build. The broker is the cheap part. The directory is the product.
THE FOUR NUMBERS
Verdict
The broker is the cheap part. At 2.36 MiB it is close to free, and four of the five components of a working namespace are an afternoon of unremarkable code.
The fifth cannot be obtained from a broker at any effort, because the information is not in there to be had. Something must hold a catalogue of what exists, what it means, what it should be publishing and whether it has gone quiet. That catalogue has to be maintained as the plant changes, and maintaining it is the actual job.
So the buy-versus-build question is not "can I run Mosquitto instead of a product". Obviously you can. It is whether you want to own a catalogue and a validator and keep them true for a decade.
Three sites, a stable tag list and an engineer who enjoys this: build it, and you will understand your own data better for having done so. Forty sites and a new one every quarter: the catalogue is the product, and that is what you are buying.
The thing nobody should do is build the first four parts, skip the fifth, and call the result a Unified Namespace.
The argument, without the measurements, is in the article.
Newsletter
New essays, by email.
SCADA, cloud, AI, and the plant floor — a short email when something new is published. No noise, unsubscribe anytime.