jonnify·bcs /bcs/elixir-core/champ/the-crossover

B2.3 · dive 3 — the crossover

The forest buys time travel and pays in chronology.

H6 builds both stores at twenty thousand rows and refuses to pick a winner it did not measure: the flat table wins the present, the forest wins history categorically — and the trade the trie makes silently is named here rather than discovered later.

Source: content/bcs2.3.md; the gates are H4–H7 in bcs_rung_2_3_check.out; the comparison arm is Chapter 2.2's ETS store.

The crossover, drawn. Four measured axes between the two store classes. Select an axis to read the verbatim figures and which side it falls to.
build 41 ms vs 7 ms → the flat table point read 799 ns vs 315 ns → the flat table snapshot 1000 in 90 us vs one in 2540 us → the forest chronology hash order → the flat table
readout · Select an axis. Three are H6's measurements at twenty thousand rows; the fourth is the trade the trie makes silently, named in §3 below.
§1

The transcript

bcs_rung_2_3_check.out · verbatim · this dive reads H4–H7
content/echo_data/runtimes/elixir/bcs_rung_2_3_check.out · the H4–H7 lines, verbatimfrozen
H4 forest ok -- two kinds, one forest: namespace_size AST=500 ORD=500 from the cached census, partition exact
H5 hotpath ok -- by-snowflake 1858 ns/op vs string-id 2344 ns/op over 20000 puts
H6 crossover ok -- at 20000 rows -- build: champ 41 ms vs ets 7 ms; point read: champ 799 ns vs ets 315 ns; snapshot: 1000 champ snapshots in 90 us (a snapshot is a binding) vs one ets copy-out in 2540 us

07:31:13.583 [debug] ChampServer started with 0 entries
H7 server ok -- read scale-out: 10000 server calls 28 ms vs snapshot-once-then-pure 8 ms (copy cost included)
PASS 7/7

The full record, H1–H3 included, is frozen on the module hub. The [debug] line stays — the drop logs its starts, and the record keeps the drop's voice.

§2

H4 and H5 — the census and the hot path

content/bcs2.3.md · What · When

H4 interleaves five hundred instruments and five hundred orders into one structure and reads the partition exactly — namespace_size AST=500 ORD=500 from the cached census, every get_namespace row carrying its prefix. Two kinds, one forest; the brand keeps them apart without a second structure.

H5 times the integer lane: by-snowflake 1858 ns/op vs string-id 2344 ns/op over twenty thousand puts with the mint included in both arms — the lane a queue consumer holding raw snowflakes should take. Take the integer lane whenever the snowflake is already in hand; the string path exists for boundaries, not for loops.

§3

H6 — the crossover, stated plainly

content/bcs2.3.md · What

The flat table wins the present: build champ 41 ms vs ets 7 ms, point reads champ 799 ns vs ets 315 ns. The forest wins history categorically: a snapshot is a binding — one thousand of them in 90 microseconds — against one ets copy-out in 2540 us, a price paid per snapshot, per row count.

And one trade the trie makes silently is named here rather than discovered later: hashing scatters the keys, so the order theorem's free byte-walk does not survive — to_list/1 returns hash order, and a chronological read over the forest is a sort or a sibling ETS index. The forest buys time travel and pays in chronology.

The crossover comparator. A pure function over the committed H6/H7 figures: select an axis; the readout names the side that carries it and divides the verbatim numbers into a factor.
readout · Select an axis. The committed figures: build 41 ms against 7 ms; point read 799 ns against 315 ns; 1000 snapshots in 90 us against one copy-out in 2540 us; 10000 server calls in 28 ms against snapshot-once-then-pure in 8 ms.
§4

H7 — the snapshot-out pattern

content/bcs2.3.md · What · How

ChampServer is the thin owning process; H7 exercises the read pattern the immutability enables: get_champ/1 ships the whole structure across the process boundary once — the BEAM copies messages, so this is one O(n) copy, priced in the gate — and then reads are pure calls on the caller's own heap: 10000 server calls 28 ms vs snapshot-once-then-pure 8 ms (copy cost included). Within a process, a snapshot is free; across processes, it costs one copy and then scales without the owner.

The BEAM's economics do not port — structural sharing rides an immutable heap and a sharing-aware GC — so the honest Go counterpart is not a persistent trie but the epoch snapshot: an atomic.Pointer[map[string]V] the owner swaps after copy-on-write batches, readers loading the pointer lock-free and holding their epoch as long as they please:

cur := state.Load()              // a snapshot is a pointer load
next := maps.Clone(*cur)         // the owner pays the copy, batched
next[id] = v
state.Store(&next)               // readers on old epochs are undisturbed

Coarser grain, same contract: a snapshot is a thing you hold, not a thing you rebuild — and if the epochs shard, they shard by the same hash32.

§5

The tense litmus

content/bcs2.3.md · When

One question decides the store class: does any read ask about a moment other than now? If yes — versions, as-of, forks, undo — the forest is the representation and the per-write path copy is fair rent. If no, Chapter 2.2's flat table wins on every measured axis and should be chosen without sentiment. Hybrids are legitimate and ordinary: the present in ETS for point speed and ordered walks, periodic forest snapshots for history — two representations, one identity key, no translation layer because the key is the same fourteen bytes everywhere.

The litmus, applied. Select the tense of the workload's reads; the readout names the store class the manuscript assigns.
readout · Select a tense. Static verdicts — only now → the flat table · other moments → the forest · both → a hybrid over the shared key.
§6

References

sources · related

Sources

Related