jonnify·bcs /bcs/trading/engine/the-disruptor-seat

B8.1 · dive 1 — the Disruptor seat

Admission as a typed answer at the door.

The ingress half of the engine answers one question: in what order, and at what cost of admission. EchoCache.Ring is the seat — real, shipped Elixir at echo/apps/echo_cache/lib/echo_cache/ring.ex, a bounded buffer whose publish/2 answers :ok or :dropped with the drop counted, drained by exactly one process. Admission control is a typed answer at the door rather than an incident discovered as latency. The committed gate fixes the shape: 64 accepted, 136 refused with :dropped and counted — never blocked, never overwritten.

Source: the Ring is real umbrella source at echo/apps/echo_cache/lib/echo_cache/ring.ex, actively hardened by the rung-gated EchoMQ program (docs/echo_mq/emq.roadmap.md); its behavior is also on the committed B4.3 record (bcs_rung_4_3_check.out, PASS 6/6). The drainer in front of it, Exchange.Book — the trading engine on top — is PROPOSED (docs/trading/), taught as design because it has run no rung, never the substrate.

The ring-admission simulator. A bounded buffer of capacity 64 — the committed full drill's shape. Publish answers :ok until the ring is full, then :dropped with a running count; release the held applier to drain. Every readout line is the structure's own arithmetic.
capacity 64 · publish fills a slot; full → :dropped, counted, never overwritten the applier drains everything between head and tail in one pass — one writer, batch-shaped a fresh ring stands at occupancy 0; the bound bends the at-most-once contract no further than it already bends
readout · A fresh ring stands at occupancy 0 — accepted 0, dropped 0, of capacity 64. Publish to fill slots; the full drill publishes 200 against the held applier — expect 64 accepted and 136 dropped, the committed shape. Release the applier to drain.
§1

The pattern, at its source

docs/trading/trading.patterns.md · Fowler · the Disruptor paper

LMAX's published architecture centers a Business Logic Processor that runs entirely in memory by event sourcing and handles "6 million orders per second on a single thread" (Fowler), surrounded by Disruptors — pre-allocated ring buffers where producers claim slots, a single writer applies in batches, and consumers chase sequence numbers without locks. The team's reported conclusion is the part that generalizes: contended queues are at odds with how modern CPUs run, and the cure is the single-writer principle plus mechanical sympathy — one owner per mutable thing, batch when behind, make waiting policy explicit.

The 6-million figure is external, and it is quoted with its attribution. It is not a number the trading engine claims; the Exchange.* suite on top of the Ring has run no rung yet. The Ring itself is real and measured — its numbers are on the B4.3 record. The engine takes the principles, not the count.

§2

What transfers, and what does not

docs/trading/trading.patterns.md · the design's reasoning

The BEAM cannot and should not imitate the Java mechanics — and does not need to, because the principles transfer without them. Select an element to read why it transfers whole or why the BEAM deliberately drops it.

LMAX elementVerdictWhy
bounded buffer + overload answertransfers wholethe Ring's publish/2 answers :ok or :dropped, the drop counted
single writer, batched appliestransfers wholeone drainer per book, woken to apply whole batches in arrival order
sequence as coordinationtransfers wholethe sequence is the branded Snowflake stamped at admission — one ordering for all
journaling beside the hot pathtransfers wholerecovery is replay over the log — the next module's subject
busy-spin wait strategiesdoes not transferscheduler-hostile on the BEAM; the Ring parks and the wake is a message
cache-line padding / slot pre-alloc across heapsdoes not transferthe win evaporates across heap boundaries; the BEAM's copying isolation is already paid
multi-consumer barriers on the hot bufferdoes not transferdownstream consumers read the log, never the ingress buffer — the Ring stays a seat, not a bus
readout · Select a row to read the verdict and its reason. Four elements transfer whole; three the BEAM drops deliberately, and nothing is lost in the omission.
§3

The seat is exact, not analogical

echo/apps/echo_cache/lib/echo_cache/ring.ex · B4.3 record

The seat is real source, and it matches the pattern part for part. The moduledoc names the correspondence in the house's own words (source: echo/apps/echo_cache/lib/echo_cache/ring.ex): "This is the Disruptor's shape translated to the BEAM — sequences, preallocated slots, batched consumption — standing beside the bus's park-don't-poll: both replace discovery with arrival," and "the single-producer requirement is structural, not advisory." The committed B4.3 record carries the shape in numbers — the bound, the gauge, the batch-shaped drain — every figure verbatim from the ring's own rung:

content/echo_data/runtimes/elixir/bcs_rung_4_3_check.out · the slice this dive readsfrozen
G4 full ok -- the bound held its shape: 64 accepted, 136 refused with :dropped and counted -- never blocked, never overwritten -- then the release drained all 64 and publish 201 landed and applied: a storm bends the lane's at-most-once contract no further than the contract already bends
G3 occupancy ok -- mid-storm the gauge read 600 of 4096 and drained to exactly 0; priced, the ring moved 100000 items in 99 ms -- 1005116 items per second end to end on one scheduler, inside the derived band, largest batch 200, nothing dropped
G1 surface ok -- the ring's surface is whole -- publish, occupancy, stats, stop, a generic one-batch apply function -- ... a fresh ring stands at occupancy 0
PASS 6/6

The bound is real: 64 accepted, 136 refused with :dropped and counted, then the release drains all 64 and the next publish lands. The gauge is observable: mid-storm the gauge read 600 of 4096 and drained to exactly 0. The drain is single-consumer and batch-shaped, with the largest batch a reported stat: largest batch 200, nothing dropped. The ring under storm refuses, recovers, and keeps serving — exactly the admission contract a match path needs. The Exchange.Book that drains it is PROPOSED: the single writer, woken to drain whole batches, shelling out to a pure Decider — the trading engine on top of a real, hardened substrate, taught as design because it has run no rung, and so it claims no number; the Ring beneath it is shipped and measured.

§4

The chase sequence is the id

docs/trading/trading.specs.md · the order theorem

The one twist this house adds to the pattern: the chase sequence is not a slot counter beside the data but the branded Snowflake inside it. Mint order is the sequence, so a consumer's resume cursor is an id — EchoData.BrandedTree.page_after/4 reads strictly after it in creation order — the same order holds in every store by the byte-sort theorem, and there is no second numbering scheme to reconcile with the first. A counter beside the data is a second source of truth; the id is the first one, already paid for at mint.

The alternatives are named honestly. The unbounded GenServer mailbox — the BEAM default and the proper baseline: it preserves order and the single writer for free; what it lacks is an admission answer, so overload arrives as mailbox depth, which arrives as latency. The Ring's :dropped is the same architecture with the truth told at the door. GenStage / Broadway — demand-driven pull is the right shape where the consumer sets the pace (draining work), and the wrong shape at market ingress, where producers are external and the answer must be immediate accept-or-shed. A stream as the ingress (XADD first, decide later) — durable and replayable, but one wire round trip before every decision, and it logs commands, not facts, re-opening the command-sourcing coupling the next dive names.

Where the seat is the wrong tool, stated: low-rate aggregates — an admin book, a reference-data writer — gain nothing from a Ring over a plain call; fan-out is never the Ring's job (one drainer, by law); and durability is never the Ring's job either — the Ring may drop by design, the log may not lose by design, and the two truths are different and both stated. The pure core that reads what an accepted command means is B8.1.2 · The Decider.

§5

References

sources · related

Sources

Related