E3 · Movement II · The Extension
EchoMQ 2.0 —
the protocol break
One deliberate, versioned fork: the wire becomes EchoMQ's own, every key declared, every queue placed.
Movement I taught the as-built core — the v1 line, BullMQ-wire-compatible, frozen
at 1.3.0. Movement II opens with the fork: emq.1 ships
EchoMQ 2.0 — the emq:{q}:… keyspace replacing
emq: with the per-queue hashtag applied transparently by the core, the
reserved {emq}: base for the core's own keys, every Lua key declared in
KEYS[], meta.version echomq:2.0.0
with a two-way boot fence, and an explicit v1→v2 migration path. BullMQ compatibility is dropped
by design. The rung is drafted — nothing of v2 exists on disk yet, so this
chapter teaches from the emq.1 spec ("emq.1 ships
<surface>") and is re-grounded when the rung lands.
Movement II · Extension · tracks emq.1 · drafted (the 2.0 break) ← redis-patterns R0 ← redis-patterns R2
Why & when
The v1 wire carries two structural inheritances that cap it on any placement-aware store — wherever a
key's {…} hashtag decides its placement. First, the
undeclared-keys flaw: roughly half of the 50 inherited Lua scripts (about 24–26,
pattern-dependent) build per-job keys inside the script
(local jobKey = keyPrefix .. jobId — moveToActive-11.lua:148)
and never declare them in KEYS[] — outside the engine scripting contract
(Valkey's own guidance: every key a script accesses arrives as an input key argument).
Second, the unplaced keyspace: EchoMQ.Keys interpolates the queue name
verbatim — no hashtags, so nothing for the threads to balance on. Containing both under compatibility has a
proven ceiling; resolving both means breaking the wire. So the wire breaks — once, versioned, at the first rung.
Read this chapter first if you follow the build into groups (E4), batches (E5), lifecycle controls (E6), or the cache (E7) — each rides the v2 protocol this break establishes. The interactive below shows the fork directly: the same queue's keys on the v1 line and under the 2.0 protocol emq.1 ships.
The fork · queue payments, two protocol lines
The triangle this chapter stands on
the pattern · and the evidence
R0 · foundations (load-once, SHA-dispatched scripts) and R2 · atomicity (a multi-key change as one indivisible Lua move) — plus the DF undeclared-keys audit, the measured flaw the break resolves.
the spec · ⇄ emq.1 · drafted — the break
The rung tracked, specs/emq/emq.1.md (rewritten 2026-06-10): the
emq:{q}:… keyspace, the {emq}: reserve, the
declared-keys script set, echomq:2.0.0 + the fence, the migration path. Taught from
the spec until the rung ships.
the as-built code — the v1 evidence
Present tense: EchoMQ.Keys interpolates verbatim (no hashtag);
moveToActive-11.lua:148,162 concatenates undeclared per-job keys;
EchoMQ.Scripts.execute_raw/4 sends only the declared list;
meta.version = bullmq:5.65.1, the line v2 freezes at 1.3.0.
Start here — the orientation
Three orientation dives take the break's big ideas in turn, before the granular modules: the keyspace EchoMQ
owns once it stops speaking emq:, the versioned fence and migration path that make
one break safe, and the conformance probes that replace compatibility as the standing check.
The owned keyspace
emq.1 ships emq:{q}:… replacing emq: — the per-queue hashtag applied transparently by the core, the {emq}: base reserved for the core's own keys. Grounds in emq.1 D1.
The versioned break
One fork, fenced: meta.version becomes echomq:2.0.0, a two-way boot fence refuses cross-version contact, and an explicit v1→v2 migration path closes the line frozen at 1.3.0. Grounds in emq.1 D3/D7.
Conformance, not compatibility
The v2 conformance probe and the fleet-interop round-trip that make "correct on its own wire" a running check — and the honest seam: stock BullMQ clients cannot speak v2, by design. Grounds in emq.1 D5/D6.
The full chapter
The orientation above is the front door; the depth is four modules and a workshop, each a hub with three dives, re-derived from the rewritten rung's deliverables — as each is authored, its card here links through.
The owned keyspace
emq.1 ships emq:{q}:… with the hashtag applied transparently by the core, and reserves {emq}: for the core itself. Dives: the emq prefix · the transparent hashtag · the {emq}: reserve.
The declared-keys script set
Every Lua key in KEYS[] — the inherited undeclared-keys flaw resolved at the root, so the engine sees each script's true key set before it runs. Dives: the flaw · the declaration discipline · the placement payoff.
The versioned fence & branded ids
meta.version echomq:2.0.0 with a two-way boot fence for mixed fleets; the integer Snowflake branded at the edge. Dives: the version fence · the snowflake · the codec-locality rule.
The two probes — conformance & fleet interop
The v2 conformance capture and the fleet round-trip that prove the owned wire every rung. Dives: the conformance probe · the fleet interop · the diff rule.
Workshop
Walk the v1→v2 migration on a real queue and run both probes green on the v2 wire — the conformance diff empty, the fleet round-trip closing.
How it works
The break is one rung with one mechanism: own the keyspace, declare the keys, version the wire. emq.1 ships
the key builders that write emq:{q}:wait, emq:{q}:active,
emq:{q}:<jobId> — the per-queue hashtag inserted by the core, never by the
caller — and a script set in which every key a script touches is listed in KEYS[]
before the script runs. The v1 line concatenates per-job keys inside the script
(local jobKey = keyPrefix .. jobId, moveToActive-11.lua:148),
outside the engine scripting contract — Valkey's guidance: every key a script accesses arrives in
KEYS[]. With every key declared and placed by its {q} hashtag,
each call's key set is slot-local and known before it runs.
meta.version records echomq:2.0.0; a two-way boot fence
refuses cross-version contact; the migration path moves a v1 deployment over once, deliberately.
A multi-key change is one indivisible Lua move over KEYS/ARGV
with no foreign command (R2), dispatched once by SHA (R0) — and the KEYS[] declaration
is load-bearing: it is how the store knows what a script will touch before it runs.
emq.1 ships the emq:{q}:… keyspace and the declared-keys script set, so every
move keeps the v1 line's atomicity and gains a true, declared key set — slot-local by
construction, no escape hatch.
The break is the one rung that ships no feature — it ships the wire, so every later rung is a set of declared-keys scripts on an owned, placed keyspace, and "correct on the v2 wire" is a check that runs.
The chapter closes with E3.05 · the workshop — walk the v1→v2 migration on a real queue and run both probes green on the v2 wire, the conformance diff empty and the fleet round-trip closing.
The 2.0 fork — EchoMQ leaves the BullMQ wire
This chapter is where the fork happens. The v1 line (frozen at 1.3.0) speaks emq: and
inherits the undeclared-keys flaw; emq.1 ships the whole break in one rung — the owned
emq:{q}:… keyspace, the {emq}: reserve, every Lua key
declared, echomq:2.0.0 behind a two-way fence, and the migration path — so EchoMQ runs
on an owned, placed keyspace of its own. Broken once, versioned, then owned.
Redis Patterns Applied
This chapter is the depth on the far side of two redis-patterns doors. The foundations it stands on — load-once, SHA-dispatched scripts and the keyspace-as-contract discipline — are taught one excerpt at a time in R0 · Overview; the atomic multi-key move the break preserves is taught in R2 · Coordination. There a reader meets each pattern once and portably; here those patterns become a protocol of EchoMQ's own — every key declared, every key placed — verified against Valkey, the conformance truth.
Up next — the rest of Movement II
E4–E8 each build on the 2.0 wire emq.1 ships — declared-keys scripts on the owned, placed keyspace. They are specified and on the way; each teaches from its rung's spec until the rung ships, then is re-grounded in the real modules.
Groups
Fair lanes and the control plane. ⇄ emq.2
Batches
Bulk consumption with group affinity. ⇄ emq.3
Lifecycle controls
TTL, distributed cancel, and checkpoints. ⇄ emq.4
EchoCache
The branded local TTL near-cache. ⇄ emq.5
Production
Conformance, telemetry, and the benchmark gate. ⇄ emq.6
Movement II tracks the EMQ extension ladder rung by rung. The ladder is drafted, so each chapter teaches from its rung's spec; building course pages does not ship the rung.
References
Sources
- BullMQ — Documentation — the wire protocol the v1 line speaks and EchoMQ 2.0 deliberately leaves.
- Valkey — Scripting with Lua — the key-declaration rule: every key a script accesses arrives as an input key argument.
- Valkey — Cluster specification — the slot placement the
{q}hashtag drives. - Redis — Cluster specification — the
{…}hash-tag rule theemq:{q}:…keyspace builds on.
Related in this course
- E3 · The owned keyspace — start here.
- E2 · The core — the as-built v1 line the break forks from.
- redis-patterns R0 · Overview — the foundations door.
- redis-patterns R2 · Coordination — the atomicity door.