Overview · dive 1 · what
The three pillars
One wire, three surfaces. The Queue distributes work, the Bus broadcasts signals, the Cache serves reads.
EchoMQ is one Valkey-native job system you own, canonical in Elixir. Above the one wire it owns stand three pillars, and each answers a different question about moving data between the parts of a system: the Queue hands one job to one worker, the Bus announces one fact to many listeners, and the Cache keeps one hot value close. This dive names the three surfaces and the real function that opens each one; the deep mechanism is the pillar chapters on the course map. Pick a pillar on the right to read its job and its shape.
Three pillars over one wire · pick a pillar
Three surfaces, one wire
EchoMQ owns one Valkey keyspace and one set of atomic Lua scripts. Above that line sit three product surfaces, and the difference between them is the question each one answers. The Queue distributes work: a task is handed to exactly one worker, runs once, and is marked done. The Bus broadcasts signals: a fact is announced, and every interested listener hears it. The Cache serves reads: a hot value is held close, so a read does not pay the full cost twice. Three surfaces, three jobs — one substrate underneath.
Each surface is a real module. The point here is which function is the door into each pillar; the mechanism behind the door is taught in depth in the pillar chapters.
The Queue — distribute work
A job is admitted with EchoMQ.Jobs.enqueue/4, claimed by a worker with
EchoMQ.Jobs.claim/3, and the loop that owns the claim rhythm is
EchoMQ.Consumer. Admission is one atomic step on the server: the Elixir verb passes
the job key and the pending set as the two declared keys, and the script does the rest.
# echo/apps/echo_mq — EchoMQ.Jobs.enqueue/4 # Admit a job in one atomic step. The verb passes the job key and the # pending set as the two declared KEYS; the named @enqueue script does the rest. 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} # admitted {:ok, 0} -> {:ok, :duplicate} # already present — idempotent {:error, {:server, "EMQKIND" <> _}} -> {:error, :kind} # not a JOB-namespaced id other -> other end end
The admission rule itself lives in one named Lua handle — EchoMQ.Jobs @enqueue — run on the server so the kind check, the duplicate refusal, the row write, and the pending insertion happen as one indivisible step.
-- EchoMQ.Jobs @enqueue — the atomic admission script -- KEYS[1] = the job key KEYS[2] = the pending set ARGV[1] = id ARGV[2] = payload if string.sub(ARGV[1], 1, 3) ~= 'JOB' then -- the branded-id gate: ids are JOB-namespaced return redis.error_reply('EMQKIND job id must be JOB-namespaced') end if redis.call('EXISTS', KEYS[1]) == 1 then -- already admitted? refuse, idempotently return 0 end redis.call('HSET', KEYS[1], 'state', 'pending', 'attempts', '0', 'payload', ARGV[2]) -- write the row redis.call('ZADD', KEYS[2], 0, ARGV[1]) -- same score: byte order is mint order return 1
The Bus — broadcast signals
A listener subscribes with EchoMQ.Events.subscribe/2; a lifecycle fact is announced
with EchoMQ.Events.publish/5 on the per-queue channel
EchoMQ.Events.channel/1 (emq:{q}:events). One event reaches
every subscribed listener — the broadcast shape. Beyond the live broadcast, the Bus keeps a retained,
replayable event log: append order is mint order, a reader can start at an offset and read forward, and a
consumer group recovers what it missed.
# echo/apps/echo_mq — EchoMQ.Events.publish/5 # Announce a lifecycle fact to every listener on the per-queue channel. def publish(conn, queue, event, job_id, extra \\ []) do _ = Keyspace.job_key(queue, job_id) # gate the id at the key builder before the wire payload = encode_event(event, job_id, extra) case Connector.command(conn, ["PUBLISH", channel(queue), payload]) do {:ok, _n} -> :ok # delivered to live subscribers other -> other end end
The Bus has two faces: the live pub/sub broadcast above, and a retained event log a consumer can replay from an offset. The event log — mint-ordered append, read-at-offset, consumer-group recovery — is taught in depth in the Bus pillar chapter.
The Cache — serve reads
A hot value is read with EchoCache.Table.fetch/3: a hit returns from L1 (an ETS
table) in the caller's own process, and a miss falls through to a single fill that checks L2 (Valkey), runs
the declared loader, and writes both layers. The read path is the cache's whole job — one value, read
often, held close.
# echo/apps/echo_cache — EchoCache.Table.fetch/3 # Read through the cache: an L1 hit never enters the owner process; # a miss is a single-flight fill through the owner (L2, then the loader). def fetch(name, id, timeout \\ 10_000) do case EchoCache.spec(name) do :error -> {:error, :no_such_cache} {:ok, spec} -> with :ok <- gate(spec.kind, id) do # wrong-namespace id refused at the door now = System.monotonic_time(:millisecond) case :ets.lookup(name, id) do [{^id, value, expires_at, _v}] when now < expires_at -> {:ok, value, :hit} # L1 hit, in the caller's process _ -> GenServer.call(name, {:fill, id}, timeout) # single-flight fill: :l2 or :fill end end end end
Three messaging shapes
The three pillars are three classic messaging shapes, and naming the shape is the fastest way to know which pillar a need belongs to. A queue is point to point: a message goes to one consumer, and a second consumer competing on the same queue gets a different message, never a copy. A bus is one to many: a message goes to every subscriber, each getting its own copy. A cache is neither — it is a read path: the same value is served to whoever asks, again and again, until it expires.
- The Queue
- Point to point. One job is claimed by exactly one worker. Competing workers share the load; no worker sees another's job.
- The Bus
- One to many. One event is delivered to every subscriber, each with its own copy. A new subscriber changes nothing for the others.
- The Cache
- Read through. One value is served to every reader until it expires. A miss fills once; the herd reads the single answer.
Name the shape, and the pillar names itself. One job to one worker is the Queue; one fact to many listeners is the Bus; one value read often is the Cache.
The Redis pattern catalog teaches each shape on its own — reliable queues for work distribution, streams and events for broadcast and replay, the cache-aside family for the read path. Three patterns, taught separately.
EchoMQ applies all three over one owned keyspace and one Lua layer —
EchoMQ.Jobs.enqueue/4 + claim/3,
EchoMQ.Events.subscribe/2 + publish/5, and
EchoCache.Table.fetch/3. Three surfaces, one substrate.
Which pillar fits the need
Match a real need to its pillar. Pick a need below; the readout names the pillar, the shape it fits, and the surface that does it. The rule is the shape: does the data go to one worker, to many listeners, or back to readers until it goes stale?
A need · which pillar fits it
Some needs ride more than one pillar — an order that settles is broadcast on the Bus and may invalidate a cached read. The Overview names the surfaces; the pillar chapters show how they compose. Next: the protocol below the line that lets them share one wire.
Recap — the three pillars
EchoMQ is one system with three surfaces over one owned wire. The Queue distributes work
point to point (EchoMQ.Jobs.enqueue/4, claim/3,
EchoMQ.Consumer). The Bus broadcasts signals one to many
(EchoMQ.Events.subscribe/2, publish/5) and keeps a
replayable log. The Cache serves reads through two layers
(EchoCache.Table.fetch/3). Each is named here and taught in depth in its own pillar
chapter. The next dive draws the line that lets all three share one wire — the protocol below the
language line.
Next in the Overview: The protocol below the line — why the three pillars interoperate, and why any runtime can speak them.
References
Where the three pillars are grounded, and where the engine and the dispatch they share are documented.
Sources
- Valkey — Documentation — the BSD-licensed, foundation-governed store EchoMQ is backed by — the substrate of record for all three pillars.
- Redis — EVALSHA — the load-once, run-by-SHA dispatch each pillar's atomic script runs through.
- Redis — Documentation — the data structures the keyspace under the three pillars is built from.
- DragonflyDB — Server flags —
--lock_on_hashtags: the thread-per-shard engine the declared-key, per-queue-hashtag keyspace is built for.
Related in this course
- Overview — the chapter frame (the pager prev).
- The protocol below the line — the next dive: why the pillars share one wire.
- The Protocol — the keyspace and the Lua layer the three pillars share.
- redis-patterns · Patterns become protocol — the near side of the door.
- The Branded Component System — the architecture EchoMQ is the bus, the broadcast, and the near-cache of.