jonnify·redis /redis-patterns/production-operations/operating-echomq/cluster-colocation

R8.06.1 · Operating EchoMQ — dive 1

Cluster colocation.

A single-node Valkey lets a Lua script touch any keys at all. A Valkey Cluster does not — a command may only touch keys in one of 16384 slots, or it is refused. A queue's keys must all land on one slot, and the keyspace makes them.

Grounding: EchoMQ.Keyspace.slot/1 and hashtag/1 (echo/apps/echo_mq/lib/echo_mq/keyspace.ex) · the vector slot("123456789") == 12739 · the engine is Valkey 9. The braced keyspace figure is bcs.3.md.

Three keys, one queue, one slot. Select a queue key and watch its slot — the hashtag is the same for all three, so the slot is too.
one slothashtag = q emq:{q}:pending emq:{q}:active emq:{q}:job:<JOB>
readout · Select a key. Every key of one queue carries the same {q} hashtag, so the slot is computed from q alone and all three land on the same slot.

Static verdicts — all three keys carry the {q} hashtag, so slot/1 computes the same slot for each; the whole queue is colocated, and a script over it stays in one slot.

§1

The hashtag and the slot

keyspace.ex · slot/1, hashtag/1

Valkey Cluster gives applications one lever over placement: a hashtag. If a key contains a {…} substring, the slot is computed from the bytes inside the braces alone, not the whole key. So emq:{orders}:pending and emq:{orders}:active both hash on orders — the same slot — even though the full keys differ. EchoMQ builds every queue key braced: the queue name is the hashtag, every key of one queue shares it, and so every key of one queue lands on one slot.

EchoMQ.Keyspace.slot/1 computes that slot client-side: hashtag/1 returns the substring inside the first {…} when present and non-empty, else the whole key; slot/1 is rem(crc16(hashtag(key), 0), 16384) — CRC16-XMODEM over the hashtag, modulo 16384, the cluster specification's exact algorithm. Computing it client-side means the connector can route a key to its node, and partition by slot, without a server round trip. The source pins one known vector: slot("123456789") == 12739 (CRC16 0x31C3).

echo/apps/echo_mq/lib/echo_mq/keyspace.ex · the cluster slot, computed client-sidefrozen
# Cluster slot of a key, computed client-side: CRC16-XMODEM over the
# hashtag % 16384. Known vector: slot("123456789") == 12739 (0x31C3).
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: the whole key hashes
  end
end

Notes on ValkeyValkey Cluster splits the keyspace into 16384 hash slots, and a key is mapped to a slot by CRC16(key) mod 16384; when a key holds a {hashtag}, only the substring inside the braces is hashed, so an application can force related keys onto one slot — valkey.io/topics/cluster-spec.

A queue's keys are colocated by construction: every key carries the queue name as its {q} hashtag, so slot/1 places them all on one of 16384 slots from the same few bytes.

§3

Colocation and the gate are one rule

born braced, branded, declared

The braced key is the same key the keyspace requires for the gate. A JOB id is gated at the key builder, every key is born braced, and every key a Lua script touches is declared in its KEYS[]. Colocation and the gate are two readings of one rule: every key of a queue draws from one braced grammar. The slot is computed from the brace; the gate is enforced at the brace; the script declares the braced keys it touches. One discipline serves placement and safety at once.

# the queue name is the hashtag — every key of one queue shares it.
emq:{orders}:pending
emq:{orders}:active
emq:{orders}:job:JOB0N…        # the branded JOB id, gated at the key builder
# slot("orders") is the same for all three → one slot → one atomic EVAL.
§4

The bridge — hashtag to colocation

pattern → application
the pattern

Colocate related keys on one cluster slot: a hashtag forces a key family onto one of 16384 slots, so a multi-key command stays in one slot and runs atomically.

its EchoMQ application

EchoMQ.Keyspace.slot/1 is rem(crc16(hashtag(key), 0), 16384) over the {q} hashtag; every key of a queue shares the brace, so a queue's multi-key Lua EVAL is always CROSSSLOT-safe in cluster mode.

The hashtag is the shard key. EchoMQ braces the queue name into every key, so a queue's keys are one slot and its scripts are one atomic step — the colocation the bus depends on, by construction.

§5

The production angle

deploy on a cluster

Deploy the bus on a Valkey Cluster and nothing in the application changes. Each queue's keys already carry the {q} hashtag, so each queue already lands on one slot; the cluster spreads queues across slots and nodes for you, and each queue's scripts stay legal because they never leave their slot. The hashtag is the shard key, and the operator chose it the moment the queue was named.

the door · codemojex on the colocated keyspace

Where the colocation runs

For codemojex, the cm queue's keys share the {cm} brace — one slot — and the bot workers draining cm all address that one slot; the colocation is automatic, the same as any queue. The deeper protocol — the full Lua inventory, the lane and lease mechanics that run on those colocated keys — is the dedicated EchoMQ Queue pillar. For the braced keyspace as the architecture, see the bus; for where it runs in production, Codemojex on Fly.

Colocation is a deployment property the keyspace already guarantees. Name a queue and its keys are one slot on any cluster — the operator gets atomic multi-key scripts with no manual sharding.

§6

Recap — the brace is the shard key

next → R8.06.2 · prometheus-and-opentelemetry

EchoMQ.Keyspace.slot/1 is rem(crc16(hashtag(key), 0), 16384) over the {q} hashtag, vector slot("123456789") == 12739. Because every key of a queue shares the brace, all land on one slot, so a multi-key Lua EVAL over a queue is always CROSSSLOT-safe — colocation by construction. That is the placement move of operating the bus. Next, the observability move: the telemetry tree a standard exporter attaches to.

§7

References

sources · related

Sources

Related in this course