On this page
Schema
§7.1.1 Storage model
The metric store uses a single SQLite database. Unlike event and log storage which store individual records as rows, the metric store is organised around time series. A time series is a unique combination of metric name, labels, and type. Individual data points (samples) are appended to their time series over time.
§7.1.2 Series table
The series table maintains the registry of known time series:
| Column | Type | Description |
|---|---|---|
id |
INTEGER PRIMARY KEY | Series identifier. Auto-assigned. Used as a foreign key in the samples table. |
name |
TEXT NOT NULL | Metric name (e.g., cpu.usage). |
labels |
TEXT NOT NULL | Canonical label representation. Labels are sorted by key and encoded as a comma-separated key=value string (e.g., core=0,host=server1). The empty string represents no labels. |
type |
INTEGER NOT NULL | Metric type: 0 = counter, 1 = gauge, 2 = histogram. |
label_hash |
INTEGER NOT NULL | Hash of the canonical label string. Used for fast lookup. |
The combination of name and labels MUST be unique. The label_hash accelerates lookup but is not a uniqueness constraint -- collisions are resolved by comparing the full labels string.
§7.1.3 Samples table
The samples table stores individual data points:
| Column | Type | Description |
|---|---|---|
series_id |
INTEGER NOT NULL | Foreign key referencing series(id). |
timestamp |
INTEGER NOT NULL | Wall clock time in nanoseconds since Unix epoch. |
value |
REAL NOT NULL | The numeric value. For counters and gauges, this is the raw value. For histograms, this column stores 0 and the histogram data is stored in histogram_data. |
histogram_data |
BLOB | Msgpack-encoded histogram value (boundaries, counts, total_count, sum). NULL for counter and gauge samples. |
§7.1.4 Write-time indexes
At database creation, eventd MUST create the following indexes:
idx_samples_series_timestamponsamples(series_id, timestamp)-- the primary query pattern is "give me samples for series X in time range Y." This composite index supports both series lookup and time range filtering in a single index scan.idx_series_nameonseries(name)-- required for metric name lookups.idx_series_label_hashonseries(label_hash)-- required for fast series resolution when ingesting data points.
§7.1.5 Series resolution
When a data point arrives, eventd MUST resolve it to a series ID:
- Compute the canonical label string (sort labels by key, encode as
key=valuepairs). - Hash the canonical label string.
- Look up the
seriestable bynameandlabel_hash. - If a match is found, verify the full
labelsstring matches (hash collision check). Use the existingseries_id. - If no match is found, insert a new row into the
seriestable and use the newseries_id.
Series resolution MUST be cached in memory for the lifetime of the eventd process. The series table is expected to be small (thousands to tens of thousands of time series on a typical system). After initial population, series resolution is a hash table lookup with no SQLite query on the hot path.
§7.1.6 Schema versioning
The metric store database MUST contain a metadata table with the same structure as the event and log stores. The schema_version for the metric store is 1.