Expand description
Block-level numeric ranges — the zone map.
crate::bloom answers “could this block hold this exact value”, which is
everything an equality needs and nothing an ordering can use. The queries
that actually open a tracing UI are orderings: “spans slower than a
second”, “anything that returned 5xx”. Both scan every block in retention
today, because duration_nano > 1_000_000_000 has no value to hash.
So a second sidecar, holding one (min, max) pair per numeric thing the
block contains — every numeric column of the root table, and every attribute
key with a numeric value at any level. A term whose range cannot reach the
block’s is a block not opened. Reading it is a read(2) of a few kilobytes
and a binary search, against the tens of megabytes it decides not to fault
in.
Three properties make it safe to skip a block on this file’s word, and all three are the reason the build side is fussier than “call min and max”:
- Absent key means prune. The map is complete over the block, so a key
with no entry is a key with no comparable value — no row can satisfy an
ordering against it. That is only true because
indexis driven off the schema, exactly ascrate::attrs::indexis: a signal that grows a fourth attribute level is covered without anyone remembering it here. - Strings that look like numbers are numbers.
query::attr_matchesparses astr-typed attribute before an ordered comparison, because half the SDKs emithttp.response.status_codeas text. A range built only from theintanddoublecolumns would therefore prune away blocks holding"503", so parseable text is folded into the double range. - Strings that do not look like numbers fall back to a lexicographic
compare, which no interval over the reals describes. A key with any of
those gets
Range::ANY— the entry exists, and it answers “maybe” to everything. It costs the pruning for that one key rather than the correctness of the file.
Two ranges per key rather than one, i64 beside f64, because the query
layer compares integers as integers: an i64 past 2⁵³ rounds when it becomes
a double, and rounding a max down is exactly the false negative this whole
module is not allowed to produce. The pair costs 32 bytes on entries that
number in the dozens.
Like every sidecar here it fails open: unreadable, short or unrecognized is “scan the block”.
Structs§
- Builder
- Accumulates the ranges of one block.
- Map
- A zone map checked out, with its header validated once.
- Probe
- One ordered term, in the form a zone map answers.
- Range
- What one key’s values span, in whichever of the two number lines they live on.
Constants§
- ENTRY 🔒
- key 8 | int_min 8 | int_max 8 | dbl_min 8 | dbl_max 8
- HEADER 🔒
- magic 4 | version 1 | pad 3 | entries 4 | crc32 4
- MAGIC 🔒
- MAX_
KEYS 🔒 - Beyond this many distinct keys the map stops being a rounding error on the block and a block that diverse prunes little anyway. Past it we write nothing, which the reader reads as “scan me”.
- VERSION 🔒
- ZONE_
IDX - Filename inside a published block. Part of the on-disk format.
Functions§
- attr_
key - Hash of an attribute key, in its own domain so that an attribute and a root column of the same name do not share an entry.
- field_
key - Hash of a root-table column name. See
attr_key. - index
- Build a block’s
ZONE_IDXover its root columns and every attribute table in it. - index_
attrs 🔒 - Every attribute key with a comparable value, under
attr_key. Column positions matchattrs::index_table, which walks the same schema. - index_
root 🔒 - Every numeric column of the root table, under
field_key. - mix 🔒
- splitmix64’s finalizer, as in
crate::bloom: the domain constant above only separates the two spaces if every input bit reaches every output bit. - reachable 🔒
- Could any value in
[min, max]satisfyx op target?