Time-series & monitoring
Ingest high-rate time-series, downsample with tumbling-window queries, and expire old data with lazy TTL and age-based tiering — one engine, no rollup pipeline.
The problem
Why this is hard today
Time-series workloads have two conflicting needs: keep raw resolution long enough to investigate, but do not let raw data grow without bound. Meeting both usually means a hot store for recent high-resolution data, a rollup job that downsamples into a warehouse, and a retention script that deletes — three moving parts around one stream.
The rollup job is a stream processor in disguise, and the retention script is a background scanner that competes with ingest for I/O. Both are operational surface that exists only because the store cannot age and downsample its own data.
What time-series needs is an engine that ingests at rate, downsamples continuously into coarser tables, and expires raw data lazily as part of normal compaction — with the retention policy attached to the table, not run beside it.
Architecture
How NYXDB does it
Raw readings append; tumbling-window queries downsample them into rollup tables; a ttl predicate expires raw data lazily during compaction and age-based movement tiers older parts to colder pools (ADR-019) — the whole lifecycle is table policy, not a pipeline.
- 01
Ingest raw
High-rate readings append to a columnar table.
- 02
Downsample
A tumble() window rolls raw into 1-minute (then hourly) buckets — avg/max or open/high/low/close; a transform can materialize the rollup.
- 03
Expire
A ttl predicate drops raw rows lazily at compaction — no background scanner competing with ingest.
- 04
Tier
Age-based part movement relocates older parts to colder pools.
Real SQL
In practice
CREATE TABLE metrics ( series String NOT NULL, value Float64, ts DateTime) SETTINGS mode='append', layout='columnar', ttl='ts < now() - INTERVAL 30 DAY';STREAM SELECT series, window_start, avg(value) AS avg_v, max(value) AS max_vFROM tumble(metrics, ts, INTERVAL 1 minute)GROUP BY series, window_startEMIT AFTER WINDOW CLOSE;STREAM SELECT window_start, arg_min(price, ts) AS open, max(price) AS high, min(price) AS low, arg_max(price, ts) AS closeFROM tumble(trades, ts, INTERVAL 1 minute)GROUP BY window_startEMIT AFTER WINDOW CLOSE;Every statement follows the engine’s own test SQL shapes. See the SQL reference for full syntax.
Capabilities
What you get
High-rate ingest
Columnar append absorbs high-rate series.
Tumbling & hopping windows
tumble() / hop() downsample by time bucket.
OHLC in SQL
arg_min / arg_max / max / min build candles over a window.
Lazy TTL
A ttl predicate expires raw rows at compaction — no background scanner (ADR-019).
Age-based tiering
Older parts move to colder pools by age (ADR-019).
Continuous rollups
Transforms materialize downsampled tables (ADR-080).
Proof
Measured on the vetted benchmark lane
durable ingest, single core
1M-row batches (~232B, 16-col)
group-fsync default
View benchmark~2900×skip-index pruning (minmax, 16 parts)
time-range scans skip parts
vetted table
View benchmark952M rows/ssingle-shard columnar scan
vetted table
View benchmarkMeasured on Apple M4 Max (dev), macOS — server-class validation pending. Release build, median of 5, commit-pinned (d4a3885b, 2026-07-07). Ingest figures are engine-side. See the full benchmark suite.
One engine for the whole time-series lifecycle
Ingest, downsample, and expire — all in SQL.