jonnifyechomq

The owned keyspace · dive 03 · the reserve

The reserve

Some state belongs to the wire, not a queue. It needs a home no queue can occupy.

Not everything the protocol stores belongs to a queue — the wire identity is one record for the whole node. It needs a home no queue can name. That home is the reserve: {emq}:, built by EchoMQ.Keyspace.reserve/1, holding version_key/0 → {emq}:version. The reserve is first-byte-disjoint from every per-queue emq: key — so a queue named emq still cannot collide with it. Who writes the value depends on the executor: a wire 4 connector claims an absent one; on a node that loads the native module an administrative install writes it, and the native write verbs and a wire 6 connector only read it.

Two spaces · pick one to read its first byte

e emq:{orders}:pending per-queue · first byte 'e' { {emq}:version reserve · first byte '{'
a per-queue key — e.g. emq:{orders}:pending. First byte: "e". Hashtag: orders → slot 105. Every queue key starts with the literal emq: — first byte "e".

First-byte-disjoint, by construction

The reserve exists because some state is the wire's own: the wire identity, one record for the whole node, shared by every queue and every speaker. It needs a home that no queue can occupy. The mechanism is structural and total: the reserve and the per-queue space differ in their first byte.

A per-queue key starts with the literal emq: — emq:{orders}:pending begins with the byte e. A reserve key starts with {emq}: — it begins with the byte {. The two spaces can never share a prefix, so a key in one is never mistaken for a key in the other. The reserve is also its own hashtag: {emq} routes every cross-queue key to one slot, slot 9051, the same way a queue's {q} does. A queue whose name hashes there (the queue named emq does) shares that slot, never a key.

emq:{q}:<type>
per-queue space — first byte e; the hashtag is the queue (orders → slot 105).
{emq}:<suffix>
the reserve — first byte {; the hashtag is emq → slot 9051.

reserve/1 & version_key/0 — one base, one key in use

The reserve is a module constant; reserve/1 appends a suffix to it, and version_key/0 is the one reserved key in use: the wire identity, the key the version fence reads.

# echo_mq — EchoMQ.Keyspace
# The reserve is the braced base {emq}: — the cross-queue space, structurally
# disjoint from a queue's emq: prefix (different first byte). reserve/1 builds
# any cross-queue key; version_key/0 is the one in use: the wire identity.
@reserve "{emq}:"
@version_key @reserve <> "version"
# … queue_key/2, valid_queue?/1, valid_queue!/1 and job_key/2 sit between.
@spec reserve(binary()) :: binary()
def reserve(suffix) when is_binary(suffix), do: @reserve <> suffix

@spec version_key() :: binary()
def version_key, do: @version_key

So version_key() is {emq}:version, and reserve("anything") is {emq}:anything. Every cross-queue key the core needs is built from the one reserved base, so there is exactly one place the wire's own state lives — and it is a place no queue can name.

The native module mirrors the same two constants, beside the value it requires in that key:

// trafikey — the native keyspace mirror
// The same reserved key version_key/0 builds, the same braced base as
// reserve/1, and the wire identity this module requires in that key
// before any write.
pub const VERSION_KEY: &[u8] = b"{emq}:version";
pub const RESERVE: &[u8] = b"{emq}:";
pub const EPOCH: &[u8] = b"echomq:6.0.0";

Two claim paths — who writes the reserve

The key is one. Who writes it, and who only reads it, depends on the executor. Because {emq}:version is first-byte-disjoint from every queue's keys, no queue's traffic can touch it and no queue can shadow it; the protocol writes it on the two paths below.

A wire 4 connector claims it

EchoMQ.Connector runs its fence on every connect, after the handshake and before the socket goes active. The fence has one clause per wire; on the default, wire 4, the connector is the scripted executor's client:

# echo_wire — EchoMQ.Connector
# The fence's wire-4 clause, the default; @wire_version is its wire identity.
# A matching value runs. An absent one is CLAIMED: SET ... NX writes only
# while the key is still empty, and the read-back GET confirms whose value
# won, so a racing first writer is caught. Any other value is refused.
defp fence(sock, buf, 4) do
  vkey = Keyspace.version_key()

  with {:ok, current, buf2} <- sync(sock, ["GET", vkey], buf) do
    case current do
      @wire_version ->
        :ok

      nil ->
        with {:ok, _, buf3} <- sync(sock, ["SET", vkey, @wire_version, "NX"], buf2),
             {:ok, @wire_version, _} <- sync(sock, ["GET", vkey], buf3) do
          :ok
        else
          {:ok, got, _} -> {:error, {:version_fence, got}}
          err -> err
        end

      got ->
        {:error, {:version_fence, got}}
    end
  end
end

@wire_version is "echomq:4.0.0". A refusal is fatal: the connector stops at start (init/1 returns {:stop, reason}) and on a reconnect.

A connection opened with wire: 6 runs the other clause, fence(sock, buf, 6), and never writes the key: it calls EMQ.VERSION, applies the client rule, then reads {emq}:version and requires echomq:6.0.0. An absent value is refused ({:version_fence, :absent}), not claimed, and every refusal is fatal.

The native module only reads it

On a node that loads the native module, ordinary clients never write {emq}:version. Every native write verb reads it after checking the call's shape and layout, before any domain check or write, as the caller (so ACL key rules apply), and requires the module's identity, echomq:6.0.0. The read never writes. An absent value refuses with EMQWIRE expected echomq:6.0.0 but {emq}:version is absent, any other string with EMQWIRE expected echomq:6.0.0 found <value>, a value of another type passes the server's WRONGTYPE through, and a refusal writes nothing.

The value is installed administratively, before ordinary clients connect: EMQ.INSTALL 1 {emq}:version. The call names the key and nothing else, so the caller cannot choose the value. Absent → it writes echomq:6.0.0 and replies 1; equal → 0, no write; a foreign value → EMQWIRE expected echomq:6.0.0 found <value>; a non-string → WRONGTYPE. It is registered admin and deny-script (ACL categories write admin dangerous slow), so no script can call it, and ACL rules can withhold it from ordinary users by category.

One node, one value

The two identities cannot share a node. A wire 4 connector claims an absent key with echomq:4.0.0, after which every native write verb refuses with EMQWIRE; and on a node that holds echomq:6.0.0, a wire 4 connector stops at connect while a wire 6 connector proceeds. So the two executors keep separate nodes: the native module runs on its own node, and the Echo service node loads no module. The mechanism of each check is the module Immutability & branded ids; here the point is where the value lives, and who may write it.

wire 4 connector
reads at connect; claims an absent value with SET … NX and a read-back; refuses any other value, fatally.
wire 6 connector
calls EMQ.VERSION and applies the client rule, then reads at connect; requires the module's identity; refuses an absent or foreign value, fatally; never writes it.
native write verb
reads as the caller, after shape and layout; requires the module's identity; never writes it.
EMQ.INSTALL
administrative; writes the module's identity into an absent key, once; refuses a foreign value.

Simulate a claim — one key, three actors

Pick what {emq}:version holds and who arrives. The page runs that actor's decision over the value, as the code above makes it: the commands it issues, its reply, whether it writes, and the value it leaves. Keep the result makes that value the new starting point, so a sequence shows the rule.

{emq}:version · stored value × actor

{emq}:version holds nothing (absent) · arriving: a wire 4 connector, at connect
1 · on the wire · GET {emq}:version → nil
2 · on the wire · SET {emq}:version echomq:4.0.0 NX → OK
3 · on the wire · GET {emq}:version → "echomq:4.0.0"
result · claims :ok — the read-back matches; the socket goes active
writes · yes · value after: "echomq:4.0.0"

Whichever identity reaches an empty node first decides which executor the node serves: a wire 4 connector's claim leaves a value every native write verb refuses, and an installed native identity stops a wire 4 connector at connect. A wire 6 connector never claims, so it cannot be the first writer.

Can a queue collide with the reserve?

A natural worry: what if a caller names a queue emq? Type any queue name below, or pick one. The page builds its pending key the way queue_key/2 does and prints the reserve key beside it, compares their first bytes and their slots, and applies valid_queue?/1. A per-queue key always starts e; the reserve always starts { — so even the queue named emq, whose key is emq:{emq}:pending, cannot collide with {emq}:version. It shares the reserve's slot, never a key.

queue_key vs the reserve · do they collide?

queue_key("emq", "pending") = emq:{emq}:pending first byte "e" · slot 9051
version_key() = {emq}:version first byte "{" · slot 9051
first bytes differ ("e" ≠ "{") → DISJOINT — the same slot as the reserve: a shared slot, never a shared key
valid_queue?("emq") → true — the host and the native layout check both accept it

The reserve is disjoint by its first byte, not by a rule a caller could break. A queue named emq is harmless: its keys still start e, and the reserve still starts {.

Pattern → implementation

the pattern (Redis Patterns Applied)

Reserve a disjoint key prefix for system-owned state, so application keys and infrastructure keys can never collide — a namespace partition by construction rather than by convention. Redis Patterns Applied R2.05 works the keyspace discipline this rests on.

the implementation (echo_mq + the native module)

EchoMQ.Keyspace.reserve/1 builds the {emq}: base, first-byte-disjoint from any queue's emq:, so the wire identity at {emq}:version has a home no queue can occupy. A wire 4 connector claims it when it is absent; a wire 6 connector and the native module only read it, and an administrative install writes it.

A reserved prefix is only safe if it cannot be reached from the application space. {emq}: earns that by a structural property — a different first byte — not by asking callers to avoid a name.

Recap — beside every queue, owned by none

The reserve is {emq}: — the cross-queue space, built by reserve/1, holding version_key/0 → {emq}:version. It is first-byte-disjoint from every per-queue emq: key, so a queue named emq cannot collide with it, and it is its own hashtag. The value in it is the wire identity, written on two paths: a wire 4 connector claims an absent value with SET NX and a read-back, and refuses a different one; on a native node an administrative EMQ.INSTALL writes it, and every native write verb and a wire 6 connector only read it, refusing an absent or foreign value. One node holds one value, so the executors keep separate nodes. With the grammar, the slot, and the reserve in hand, the where of the protocol is complete; the next module reads the what — the record hash.

Next in The Protocol: The record hash — the job HASH at the job key and the field names that are part of the wire.

References

Where the reserve's store, the claim and the read are documented.

Sources

Related in this course