F4.12 · part 1 of 3 · put & partition
Watch a branded CHAMP grow
The store starts empty. Each put takes a branded id, reads its
three-letter namespace, and drops the entry into that namespace’s partition — creating the partition
the first time a namespace appears, and adding to its CHAMP every time after. No entry ever lands in the wrong
partition, because the namespace that decides where it goes is part of the key itself.
Ten entities, inserted in order. Step through the inserts and watch the partitions appear and fill as each key routes itself by namespace.
Insert and route
The same ten keys go in every time; what changes is how far through the sequence you are. Each key’s
prefix — USR, SES, LSN,
PGE — picks its partition. Step through the inserts.
Keys inserted · select a checkpoint
inserted: 10
partitions: 4
The store grows by partition. A key never has to be told where it belongs; its namespace routes it, and a partition springs into being the first time its namespace is seen.
Advanced: put, and the 33rd entry
put/2 is two steps: take the three-character prefix, then update that one
partition. update_in with a default creates the partition’s CHAMP on first use
and adds to it thereafter, and because each partition is an immutable CHAMP the write returns a new store map
that shares every partition it did not touch — so the cost of a write is one partition’s path-copy,
not a copy of the store.
Inside a partition, the CHAMP’s root node holds up to thirty-two entries directly, one per slot of its
five-bit bitmap. The thirty-third entry to collide into a slot forces that slot to become a child node —
a second level — and the depth a lookup walks is ceil(log₃₂ n): one
level to thirty-two entries, two to about a thousand, three to about thirty thousand. The partitions in the
demo hold a handful each, so each is a single level; the restructure is what keeps a partition shallow as it
scales into the thousands.
# put/2 — route on the prefix, update one partition's CHAMP def handle_call({:put, id, value}, _from, store) do ns = binary_part(id, 0, 3) # "USR0Nb..." -> "USR" store = update_in(store[ns], fn nil -> Champ.new() |> Champ.put(id, value) # first key of a namespace champ -> Champ.put(champ, id, value) # add to the partition end) {:reply, :ok, store} # new store shares untouched partitions end
a branded key
put("LSN0Nb...", v) — the namespace is in the key.
routed into its partition
The prefix selects the CHAMP; only that partition is rebuilt, the rest shared.
Next: a Snowflake registry — now that entries are in their partitions, resolving any id back to its entry and its creation time.
References
Primary sources for this lab, and the lessons it assembles.
Sources
- Elixir —
update_in/3— updating one partition, creating it on first use. - Elixir —
binary_part/3— reading the three-letter prefix. - Elixir —
handle_call/3— serialising the write.
Related in this course
- F4.12 · Lab: build a branded CHAMP store — the lab hub.
- F4.06 · CHAMP — the node inside each partition and its bitmap.
- F4.09 · Branded CHAMP maps & GenServer — the partitioned store.
- F4 · Algorithms & Data Structures