B4.2 · Coherence by Mint Time — manuscript chapter 4.2
Twenty-nine bytes, two lanes, and no clock but the name.
Chapter 4.1 bounded staleness by the clock; this chapter bounds it by the write. The
coherence message of EchoCache is twenty-nine bytes — a cached name, a colon, and the writer's
mint-time version — and everything else falls out of the order theorem: because a branded id's payload
bytes sort in mint order, newer wins is a comparison of two names, with no coordinator, no lock,
and no clock but the one already inside every id. Two lanes carry the same message, and the committed
record prices them on one row: broadcast median 72 us fire-and-forget, job lane median 148 us
at-least-once -- the guarantee costs 2.1 times the latency.
The chapter is content/bcs4.2.md; the rung behind it is
bcs_rung_4_2_check.exs, and its committed transcript closes PASS 6/6. The
vocabulary, newer-wins, the broadcast band, the loss, the crash, the price — six gates, each asserted on
stage.
A message that carries its own order
content/bcs4.2.md · Why · DecisionsA TTL is a promise about the past — this row was true within the last N milliseconds — and for quotes on a screen that promise is enough. For a position limit, a halted instrument, or a risk parameter, it is not: the desk needs this row reflects the latest write, and the latest write is exactly what the comparison set cannot say. Valkey's own tracking sends an unversioned forget this key; Nebulex synchronizes by deletion; neither message carries an order, so a late invalidation and a fresh write race, and the loser is whoever applied last. The fix the literature reached in 1978 is a total order constructed from timestamps, and this series has carried that order in every identity since Part I. The manuscript plans the measured face-off — B4.5 · The Cache Referee — and until that chapter ships, the comparison set is characterized, never measured.
The writer's obligation is one call after the store write — the coherence:
slot declared by B4.1 · Cache-Aside at ETS Speed's table directory, now wired:
version = BrandedId.generate!("TXN") # the write's own identity
:ok = Table.put(:quotes, ast_id, "px=106.00", version)
{:ok, _heard} = Coherence.broadcast(conn, "quotes", ast_id, version)
# or, when at-least-once matters:
{:ok, :enqueued} = Coherence.enqueue(conn, "quotes", group, ast_id, version)
The version is a name
Not a counter, not a wall-clock reading, not a vector: a branded id whose payload bytes already total-order by mint time across every kind. One identity system, one more dividend.
Coherence drops; it never writes
A message means the writer already placed the newer value in L2; the receiver's only move is to discard its older L1 copy. The lane carries names, not rows.
Stale messages bounce twice
The L1 comparison in the owner and the L2 comparison in one Lua script are the same predicate in two places, because a late invalidation that can erase a newer row is a coordination bug wearing a latency costume.
The consumer lives in the application's tree
An EchoMQ consumer is a real supervised worker with a lease and a lane; hiding one inside a cache
table would falsify the supervision tree. coherence_handler/1 makes the wiring one line;
the tree stays true.
Send-only, RESP3-only
A SUBSCRIBE confirmation arrives as a push, so awaiting it on the FIFO starves the queue — the new
verb sends without enqueueing an expectation, and refuses protocol 2 with a typed
:requires_resp3.
The owner — for now
Pushes apply inline in the table's process, which is correct and measured fast; B4.3 · The Single Writer and the Ring moves application onto the ring for batching and backpressure without touching the semantics gated here.
The proof
content/bcs4.2.md · quoting bcs_rung_4_2_check.outThe full committed transcript, verbatim — six gates, F1 through F6, the derive lines and
the staged consumer kill included. The mid-record [error] GenServer … terminating block
is the F5 drill's own kill — part of the frozen record, not noise around it:
F1 surface ok -- the vocabulary is whole: channel, queue, a twenty-nine-byte payload of two names, parse refusing garbage; tables declare their lane in the directory; and the connector's push path refuses a protocol 2 connection with a typed :requires_resp3 F2 newer-wins ok -- a late stale invalidation bounced off both layers -- the L1 row survived holding px=105.00 and the L2 drop script answered 0 -- while a genuinely newer version applied and the replay of the old one stayed stale: idempotence is a comparison, not a log derive (broadcast): the lane is one PUBLISH hop on the wire whose committed sequential floor is 29,456 round trips per second, near 34 us each -- expect a median push latency between 30 and 500 us, and the receiver's apply is one ETS comparison on top F3 broadcast ok -- median push latency 72 us over 100 messages, inside the derived band; the cross-node round trip holds -- the writer put px=106.00, 3 subscribers heard the name, and the other node's next read fell through its dropped L1 to the shared L2 and answered fresh F4 loss ok -- the price of fire-and-forget, stated as a gate: :qc declared the job lane and holds no subscription, so the broadcast passed it by and it still serves px=100.00 -- bounded staleness until its own lane delivers, which is the next gate's business derive (job lane): a consumer crash after claim strands the coherence job on a lease; the reaper returns it, a second consumer applies it with token 2, and reapplication is harmless because newer-wins is a comparison -- expect attempts 2 and exactly one effective drop 01:36:22.839 [error] GenServer #PID<0.203.0> terminating ** (stop) killed Last message: {:EXIT, #PID<0.202.0>, :killed} State: %{backoff: 100, buf: "", client_name: nil, connect_timeout: 3000, counters: {:write_concurrency, #Reference<0.595650664.2344747009.170073>}, database: 0, hb_ref: #Reference<0.595650664.2344615937.170091>, heartbeat_ms: 30000, host: {127, 0, 0, 1}, label: :emq, max_pending: 10000, overloads: 0, password: nil, pending: {[], []}, pending_n: 0, port: 6390, protocol: :auto, protocol_live: 3, push_to: nil, pushes: 0, sock: #Port<0.13>} F5 job lane ok -- the lane that survives: the first consumer died holding the job, the reaper returned it, the healer applied it -- :qc dropped its stale row and now serves px=107.00 from the shared L2 -- the completed job left no row to browse, and replaying the same version answers stale: at-least-once delivery, exactly-once effect derive (price): the broadcast lane is one wire hop; the job lane pays three to five hops -- enqueue, wake, claim, complete -- so a parked consumer should land its median between 80 us and 2 ms, the same order as the bus's committed 0.3 ms end-to-end median, carrying the guarantee the push cannot F6 price ok -- the two lanes on one row: broadcast median 72 us fire-and-forget, job lane median 148 us at-least-once -- the guarantee costs 2.1 times the latency, and gates F4 and F5 are the reason a surface pays it PASS 6/6
The derive lines are part of the record: each measurement is preceded by the band the
rung derived for it from already-committed figures — the wire's sequential floor of 29,456 round trips per
second prices the PUBLISH hop, the bus's 0.3 ms end-to-end median prices the job lane's three to five hops
— and F3 and F6 land inside their bands. One production module lands
(runtimes/elixir/lib/echo_cache/coherence.ex), the table grows version-aware
(lib/echo_cache/table.ex), and the connector gains the send-only push path
(lib/echo_mq/connector.ex).
The dives
three lessons, fixed by the chapter specThe Twenty-Nine Bytes
F1 — the vocabulary is whole: channel ecc:{<table>}:coh, queue
ecc.coh.<table>, a twenty-nine-byte payload of two names, parse refusing garbage,
the typed :requires_resp3. F2 — newer-wins with teeth: the late stale invalidation
bounced off both layers; idempotence is a comparison, not a log.
The Broadcast Lane
F3 — median push latency 72 us over 100 messages, inside the derived band; the cross-node round trip, writer to reader through the shared L2. F4 — the loss stated as a gate: the job-lane table holds no subscription, the broadcast passed it by, bounded staleness until its own lane delivers.
B4.2.3The Job Lane
F5 — the crash choreography: the first consumer died holding the job, the reaper returned it, the healer applied it — at-least-once delivery, exactly-once effect. F6 — the price on one row: broadcast median 72 us fire-and-forget, job lane median 148 us at-least-once.
The lane vocabulary is channel names, queue names, and a twenty-nine-byte frame — nothing in it assumes the BEAM, which is what the Go and Node ports will hold it to. The application engine under both lanes is the next module's business: B4.3 · The Single Writer and the Ring inherits the applier the rung gated here.
References
sources · relatedSources
- Valkey — Pub/Subthe broadcast lane's contract, taken whole: at-most-once, unpersisted, lost on disconnect
- Valkey — Client-side cachingthe comparison set's deletion-shaped coherence: no version, no order
- Lamport — Time, Clocks, and the Ordering of Eventsthe total order newer-wins carries in the name
Related
- B4 — EchoCachethe chapter landing; Part IV's arc
- B3 — The Busthe bus the job lane rides
- B3.3 — The State Machine in Luathe lease, the reaper, and the fencing token under the job lane
- B3.2 — Jobs Are Entitiesthe enqueue the job lane reuses
- B2.2 — Property Stores on ETSthe stores being cached
- EchoMQ — the coursethe protocol in rung-level depth, on the far side of the door
- Redis Patterns Appliedthe substrate: pub/sub, Lua, the caching patterns
- Functional Programming in Elixirthe umbrella the runtimes live in