The Protocol · workshop · hands-on
Decode a key, trace a script
The protocol is two things you can read by hand: a key, and a script.
This page closes the chapter by doing both on real artifacts — no new surface, only the
ones already taught, exercised. Decode a key: given
emq:{quotes}:job:JOB0KHTOWnGLuC, read off the queue, compute the cluster slot, and read
the kind from the branded id. Trace a script: take the
EchoMQ.Jobs @enqueue handle, read its decoded Lua, and point at the keys it declares in
KEYS[]. Start with the decoder on the right; type any key.
The key decoder · type a key
queue
—
slot (of 16384)
—
kind
—
Decode a key — emq:{quotes}:job:JOB0KHTOWnGLuC
The grammar is fixed by EchoMQ.Keyspace. A per-queue key is the literal
emq:, the queue name in a single brace pair, then the type suffix —
emq:{q}:<type>. A job row's type is job:<id>, so
job_key/2 composes emq:{q}:job:<branded-id>. The id is
gated before it is used: an ill-formed id raises and never reaches a key.
# echo_mq — EchoMQ.Keyspace # A per-queue key is emq:{q}:<type>. A job row's type is "job:" then the # branded id, so job_key/2 is queue_key(queue, "job:") with the id appended. # The id is gated first — an ill-formed id raises, never reaches a key. def queue_key(queue, type) when is_binary(queue) and is_binary(type), do: IO.iodata_to_binary(["emq:{", queue, "}:", type]) def job_key(queue, branded) when is_binary(branded) do if EchoData.BrandedId.valid?(branded) do queue_key(queue, "job:") <> branded else raise ArgumentError, "job_key requires a valid branded id" end end
Read the key in three fields. The queue is the substring inside the first
{...} — quotes. The slot is
CRC16-XMODEM(hashtag) mod 16384, computed client-side over the hashtag so the
connector routes without a server round trip. For quotes the slot is
3378. Because the slot is taken over the hashtag, every key of one queue lands on the
same slot — the pending set, the active set, and this job row are all on
3378. The canon vector confirms the algorithm:
slot("123456789") == 12739.
# echo_mq — EchoMQ.Keyspace.slot/1 — CRC16-XMODEM over the hashtag, % 16384 # Client-side cluster routing: the connector knows the slot without asking the # server. Known vector: slot("123456789") == 12739. def slot(key) when is_binary(key), do: rem(crc16(hashtag(key), 0), 16384) def hashtag(key) do with [_, rest] <- :binary.split(key, "{"), [tag, _] when tag != "" <- :binary.split(rest, "}") do tag # the substring inside the first {...} else _ -> key # no brace pair → the whole key is its own hashtag end end
The kind is the first three bytes of the branded id. JOB0KHTOWnGLuC
is a 14-byte branded id — a three-letter namespace plus an 11-character Base62 payload.
EchoData.BrandedId.parse/1 splits it, and namespace/1 reads
the first three bytes: the kind. Here it is JOB — the exact value the
@enqueue script gates at the wire.
# echo_data — EchoData.BrandedId: a 14-byte id = 3 [A-Z] namespace ++ base62(snowflake) padded to 11. # parse/1 splits namespace from snowflake; namespace/1 reads the first 3 bytes — the "kind". iex> EchoData.BrandedId.parse("JOB0KHTOWnGLuC") {:ok, "JOB", 274557032793636864} iex> EchoData.BrandedId.namespace("JOB0KHTOWnGLuC") "JOB"
So the key decodes to queue quotes, slot 3378, kind
JOB — a job row on the queue's slot. The decoder above runs these exact steps:
pure functions mirror hashtag/1, slot/1, and
BrandedId.parse/1 over whatever you type, and a key without the
emq:{q}: grammar is named malformed rather than guessed.
A key is legible by hand: the braces give you the queue, a CRC16 over them gives you the slot, and the first three bytes of the id give you the kind.
Trace a script — @enqueue to its declared keys
A verb is a named handle over a Lua script, and the script is the protocol. Read the enqueue transition in two
beats. Beat one — the named handle. EchoMQ.Jobs.enqueue/4 builds
the two keys the script will touch — the job row and the pending set — and runs the
@enqueue handle through the connector. The keys are built host-side, by the keyspace,
and handed in declared; the values ride ARGV.
# echo_mq — EchoMQ.Jobs # Beat one: the named handle. The host builds the two keys the script touches — # the job row (KEYS[1]) and the pending set (KEYS[2]) — and hands them in # declared. The connector runs @enqueue EVALSHA-first. The values ride ARGV. def enqueue(conn, queue, job_id, payload) when is_binary(job_id) and is_binary(payload) do keys = [Keyspace.job_key(queue, job_id), Keyspace.queue_key(queue, "pending")] case Connector.eval(conn, @enqueue, keys, [job_id, payload]) do {:ok, 1} -> {:ok, :enqueued} {:ok, 0} -> {:ok, :duplicate} {:error, {:server, "EMQKIND" <> _}} -> {:error, :kind} other -> other end end
The handle resolves to a script. EVALSHA-first means the connector runs the script by its
precomputed SHA1, reloading the source once on a NOSCRIPT miss:
EchoMQ.Script.new/2 precomputes the SHA, and
EchoMQ.Connector.eval/5 dispatches. Beat two — the script body.
@enqueue admits by kind, refuses a duplicate, writes the row, and inserts the pending
entry — one atomic step, every key declared.
-- the @enqueue script (EchoMQ.Jobs) — one atomic transition, every key declared -- KEYS[1] = the job row KEYS[2] = the pending set -- ARGV[1] = the branded id ARGV[2] = the payload -- The branded-id gate: the id must be JOB-namespaced — refused at the wire. if string.sub(ARGV[1], 1, 3) ~= 'JOB' then return redis.error_reply('EMQKIND job id must be JOB-namespaced') end -- Idempotent: a row that already exists is a no-op (a duplicate enqueue). if redis.call('EXISTS', KEYS[1]) == 1 then return 0 end -- The three-field row: state, attempts, payload — the shape every speaker reads. redis.call('HSET', KEYS[1], 'state', 'pending', 'attempts', '0', 'payload', ARGV[2]) -- The pending set: the id is the member at score 0, so byte order is mint order. redis.call('ZADD', KEYS[2], 0, ARGV[1]) return 1
The script declares two keys — KEYS[1] the job row,
KEYS[2] the pending set — and constructs none in-script. That is the law: every
key a script touches is passed in KEYS[]; ARGV carries values
only. The decoded key from the first move is exactly the KEYS[1] this script writes:
emq:{quotes}:job:JOB0KHTOWnGLuC is the job row, on slot
3378, with the kind JOB the gate admits.
The script tracer · pick a verb
The tracer reads a different thing from the decoder: the decoder reads one key, the tracer reads the whole set of keys a transition touches. Both come straight off real artifacts — the handle's declared keys are the contract the script runs under.
A handle resolves to a script; a script names its keys. Trace any verb and you have the keys it touches and the order it touches them in.
Pattern → implementation
Decoding a key and tracing a script is reading the protocol with no runtime in the way — the move the Redis Patterns Applied course frames, applied here in depth.
A Valkey convention becomes a protocol when the agreement is pushed below the line — the keys plus the atomic scripts — and read directly off the wire. Patterns become protocol teaches that move.
EchoMQ.Keyspace.queue_key/2 fixes emq:{q}:<type> and
slot/1 reads the routing off the key; the EchoMQ.Jobs @enqueue
script fixes the transition with every key declared in KEYS[]. The wire, read by
hand.
The protocol is legible: a key gives you the queue, the slot, and the kind; a handle gives you the declared keys and the atomic order. Read one of each and you have read the wire.
Recap — the chapter, exercised
The chapter taught the owned keyspace, the record hash, the Lua layer, and immutability with branded ids. This
close exercised them on real artifacts: emq:{quotes}:job:JOB0KHTOWnGLuC decodes to
queue quotes, slot 3378, kind JOB; the
EchoMQ.Jobs @enqueue handle resolves to a script declaring KEYS[1]
the row and KEYS[2] the pending set. The next step is the chapter map — the three
pillars that all stand on this substrate.
Back to The Protocol — the owned keyspace, the record hash, the Lua layer, and immutability with branded ids, and the door onward to the three pillars.
References
Where the keyspace, the slot algorithm, and the scripting dispatch are grounded.
Sources
- Valkey — Documentation — the BSD-licensed, foundation-governed store EchoMQ is backed by; the substrate of record.
- Valkey — EVALSHA — the load-once, run-by-SHA dispatch the connector uses to run the
@enqueuescript. - Redis — Keyspace & hash tags — cluster key routing by the hashtag inside
{...}, the mechanism the{q}brace andslot/1use. - Redis — ZADD — the same-score sorted-set insert
@enqueueuses for the pending set, so byte order is mint order.
Related in this course
- The Protocol — the chapter this workshop closes; the owned keyspace, the record hash, the Lua layer, immutability.
- Immutability & branded ids — the last module hub: the branded-id gate this page reads off a real key.
- redis-patterns · Patterns become protocol — the near side of the door: the move from convention to protocol.
- Course home — the full six-chapter map.