Skip to main content

Module zone

Module zone 

Source
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 index is driven off the schema, exactly as crate::attrs::index is: a signal that grows a fourth attribute level is covered without anyone remembering it here.
  • Strings that look like numbers are numbers. query::attr_matches parses a str-typed attribute before an ordered comparison, because half the SDKs emit http.response.status_code as text. A range built only from the int and double columns 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_IDX over its root columns and every attribute table in it.
index_attrs 🔒
Every attribute key with a comparable value, under attr_key. Column positions match attrs::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] satisfy x op target?