F4.12 · part 3 of 3 · range by time
Query by time range
The last capability the store needs is “what was created between these two times.” Because a Snowflake puts the timestamp in its high bits, ids sort by creation time, and a time window becomes an id range: compute the id that any entry at the start would have, and the one at the end, and every entry in the window is an id between them. The query needs no separate timestamp column — the bound is computed straight from the clock.
Five lessons in the partition, at known times. Pick a window and watch the bounds pick out the entries created inside it.
A window becomes a range
The window’s start and end map to id bounds [min, max). An entry is in the
window when its id falls in that range — equivalently, when its decoded time does. The lesson at
13:35 sits before every window here, so it is never matched. Pick a window.
Time window · select one · 2026-05-31 UTC
window: 14:00–14:10
matched: 2 entries
No timestamp was stored, yet the store answered a time-range query. The id is the sort key and the clock, so a window of time and a range of ids are the same question.
Advanced: bounds, sorting, and the catch
The bound is arithmetic: a Snowflake is (milliseconds_since_epoch << 22) plus
node and sequence bits, so the smallest id at time t is (t - epoch) << 22.
Compute that for the window’s start and end and you have [min, max); the same
trick drove the database range query in F4.08, where id >= min AND id < max rode
the primary-key index with no created_at column.
The catch in memory is that a CHAMP is ordered by hash, not by key, so it cannot be sliced by an id range directly. Two honest options: keep a sorted index of ids alongside the partition — a sorted set or tree — and binary-search the bounds for a true range scan; or, when a partition is small, filter it by decoded time, which is what the demo does. Either way the namespace partition keeps the work to one entity kind, and the id-as-clock means the window never has to be stored to be queried.
# range/2 — a time window becomes id bounds over one partition def handle_call({:range, ns, from, to}, _from, store) do min = Snowflake.at(from) # (from - epoch) <<< 22 max = Snowflake.at(to) hits = store |> Map.get(ns, Champ.new()) # only this namespace's partition |> Champ.keys() |> Enum.filter(fn id -> id >= min and id < max end) # in the window {:reply, {:ok, hits}, store} end
a time window
“Everything created between 14:00 and 14:10.”
an id range
[min, max) from the clock; the partition’s entries in between.
That completes the lab — a partitioned, branded-CHAMP store that grows by namespace, resolves any id with a free timestamp, and answers time-range queries — and with it the Algorithms & Data Structures chapter. Return to the chapter overview.
References
Primary sources for this lab, and the lessons it assembles.
Sources
- Snowflake ID — Wikipedia — the time-ordered id and its bit layout.
- Elixir —
Enum.filter/2— selecting the entries in the window. - Erlang —
:gb_sets— a sorted set for a true id-range scan.
Related in this course
- F4.12 · Lab: build a branded CHAMP store — the lab hub.
- F4.08 · Branded ids & persistence — the same bounds as a database range query.
- F4.07 · Identifiers, Snowflake & branded ids — why ids sort by time.
- F4 · Algorithms & Data Structures