jonnify·redis /redis-patterns/streams-events/custom-events/reserved-name-discipline

R5.04.3 · dive 3

Reserved-name discipline.

Custom event names and payload fields are free to choose — with a few exceptions: the stored-id field, an archive suffix, and a namespace prefix are spoken for.

A stream stores its canonical record id under a field named id, a key suffix is reserved for the archive watermark, and the whole keyspace lives under a reserved prefix. Keep custom names clear of those few and every other name is yours. Select a payload field; the readout reports whether it is free or reserved.

A payload field is free, except id. Select a field name; the readout reports whether a custom event may use it.
a guess-scored payload · one field per box gameGAM… playerPLR… total480 idreserved free free free stored id
readout · Select a field name. A custom event names its entities specifically — game, player, total — and leaves id to the stream's stored record id.
§1

The id field is reserved

reserved-name-discipline.md · the stored record id

When an entry is appended, the stream stores the record's canonical id under a field named id, alongside the payload fields. A reader recovers the id from that field and treats the rest as the payload. So a payload must not also carry a field named id: the reader could not tell the stored record id from the payload value, and one would shadow the other. Name the domain's own identifier something specific — room, game, player — and leave id to the stream.

# right — the payload names its entities specifically
XADD guess-scored * game GAM0Nk3…  player PLR0…  total 480 # clear
# wrong — a payload "id" collides with the stored record id field
XADD guess-scored * id 480  game GAM0Nk3…                 # ambiguous on read

The stream stores its record id under the field id, so a payload must not name a field id — name the domain's identifiers specifically and leave id to the stream.

§2

A key suffix and a prefix are reserved

reserved-name-discipline.md · the watermark and the namespace

A stream's history can be archived, and a small cache records how far the archive has advanced — under a key formed by adding a reserved suffix to the stream's key. So a stream name must not be chosen such that its key collides with that watermark key. And every key in the system lives under a reserved namespace prefix, so a custom stream lives inside that namespace rather than beside it — the name is the part after the prefix, not a free-standing key. Two mechanical rules: do not reuse the reserved suffix as part of a stream name, and let the namespace own the prefix.

The stream key has three parts — two are owned, one is yours. Select a part of emq:{q}:stream:<name>; the readout names who owns it.
emq:{q}:stream:namespace prefix · owned <name>your channel :archivedwatermark · reserved
readout · Select a part of the stream key. The prefix and the :archived suffix are owned; the <name> in the middle is the one part a custom event chooses.

A custom stream's name is the <name> inside emq:{q}:stream:<name> — keep it clear of the reserved :archived suffix and let the namespace own the prefix.

§3

Why the discipline is small

reserved-name-discipline.md · a narrow contract

The reserved set is short because the contract is narrow: one field name for the stored id, one suffix for the archive seam, one prefix for the namespace. None of them constrains the shape of a custom event — the payload can hold any fields under any other names, and a stream can be named for any event type. The discipline is a handful of names to avoid, checked once when a new event type or payload is designed, not a schema to satisfy on every append.

Three reserved names, none constraining a custom event's shape — the discipline is a one-time check at design, not a schema enforced per append.

§4

In EchoMQ — the reserved names in EchoMQ.Stream

echo_mq/stream.ex · read/6 · put_archived/4 · stream_key/2

EchoMQ's Stream Tier makes the three reserves concrete. The record id is stored under the field id: EchoMQ.Stream.append/4 issues XADD <key> <xadd_id> id <branded> <fields…>, and on read, EchoMQ.Stream.read/6 recovers the branded id from the id field and deletes id from the payload map before returning {branded, fields_map}. A custom event that named a payload field id would have it consumed as the record id, so the field name id is reserved.

# EchoMQ.Stream — the id field is the stored record id, deleted from the payload on read (real)
EchoMQ.Stream.append(conn, "cm", "guess-scored", [{"game", game}, {"total", "480"}])
#  -> XADD emq:{cm}:stream:guess-scored <xadd_id> id <branded> game <game> total 480
EchoMQ.Stream.read(conn, "cm", "guess-scored")
#  -> {:ok, [{branded, %{"game" => game, "total" => "480"}}, …]}   ("id" recovered, then removed)

The archive seam is the second reserve. EchoMQ.Stream.put_archived/4 caches the watermark under emq:{q}:stream:<name>:archived via EchoMQ.Keyspace.queue_key/2 — the :archived sub on the stream's key. A stream name must not collide with that suffix. The third reserve is the protocol's namespace: every key is built by EchoMQ.Keyspace, braced as emq:{q}:, so a custom stream's name is the <name> in emq:{q}:stream:<name>, owned by the keyspace builder rather than a bare key. Keep custom event names and payload field names clear of id, the :archived suffix, and the emq:{q}: prefix, and the rest is the domain's to name.

In codemojex (echo/apps/codemojex), the queue is cm and the consumer surfaces — Codemojex.Guesses, Codemojex.Board, Codemojex.Scoring, Codemojex.Rooms — name their entities room, game, player rather than id, so a domain event appended to a codemojex stream leaves the id field to the record id and keeps its payload unambiguous.

Notes on ValkeyA stream entry is a set of field/value pairs, returned as a flat list on a read; the stored id field sits beside the payload pairs, which is why a payload field named id is ambiguous — valkey.io/commands/xadd.

the pattern

A few names are reserved — the stored-id field, an archive suffix, a namespace prefix; a custom event keeps clear of them.

its EchoMQ application

EchoMQ.Stream stores the record id under the field id (and read/6 deletes it from the payload), reserves emq:{q}:stream:<name>:archived for the watermark, and builds every key under the braced emq:{q}: namespace via EchoMQ.Keyspace.

Redis stores stream entry fields beside the entry id; EchoMQ reserves the id field, the :archived suffix, and the emq:{q}: prefix — a custom event keeps clear of those few.

/echomq/bus

A door, not a depth — the EchoMQ Bus pillar

The archive watermark this dive names is the seam between the live stream and the durable floor; the fold that advances it, and the full keyspace grammar, are the subject of the dedicated EchoMQ course. Open onto the Bus pillar — the retained, replayable log and its keyspace. The figures here are drawn from the Branded Component System bus chapter, /bcs/bus.

/echo-persistence

Where the watermark points

What is archived below the watermark folds to the durable page tier and, beyond it, to remote object storage behind a create-only commit fence. That floor — the durability dial a system turns — is the subject of the EchoStore persistence course. This dive reserves the seam's name; that course teaches the floor it points into.

§5

References

sources · related

Sources

Related in this course