Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Topic 30 — Time-Series Engines

Metrics workloads are the most regular data any database sees — and TSDBs are what you get when every design decision exploits that regularity: append-mostly, time-ordered, compress-by-predicting, partition-by-time, delete-by-dropping-partitions.

The problem, measured (bench lane 1, provided — runs today)

cargo run --release --bin tsdb_bench — 1 M samples per series shape, 10-second scrape interval, raw representation = 16.00 B/sample (8 B timestamp + 8 B value):

shape        delta+varint      decode
constant     11.00 B/sample    272 Msamples/s
gauge        11.00 B/sample    239 Msamples/s
counter      11.00 B/sample    320 Msamples/s
random       11.00 B/sample    329 Msamples/s

11.00 bytes per sample for all four shapes, including the constant one. A series that never changes value compresses exactly as well as uniform random noise, which is absurd on its face and is precisely the finding: delta + varint encodes the timestamp well (regular scrapes delta to a small constant) and the float not at all, because an f64 that repeats is still 8 bytes of mantissa to a varint that only knows about leading zero bytes.

So the baseline has cut 16 B to 11 B — a 1.45× win, entirely from the timestamp column — and left the larger half of every sample untouched. That is the gap Gorilla’s XOR-and-leading-zeros trick is aimed at, and it is why the paper reports single-digit bits per value rather than bytes. Predict the B/sample Gorilla achieves for each of these four shapes before you implement it; the spread between constant and random should go from zero to enormous, and if it does not, your encoder is not exploiting what the shape gives it.

0. The shape of the problem

  write path                              read path
  ~1M samples/s, tiny (ts, f64) points    "cpu > 90 for job=api over 6h"
  99.9% arrive in time order              always a TIME RANGE
  per-series arrival ~10s apart           always a LABEL SELECTOR
                                          usually an AGGREGATION
        │                                        │
        ▼                                        ▼
  amortize:  batch into per-series       index labels, not values:
  compressed chunks (Gorilla),           inverted index (name,value) -> series,
  flush to immutable time-blocks         then min/max-prune time blocks

Baseline measured here (see notes.md): the obvious codec — delta+varint timestamps, raw f64 values — lands at 11.00 B/sample regardless of value shape, because the 8-byte values dominate. The entire point of Gorilla’s XOR trick is attacking those 8 bytes.

1. The design space

graph TD
    G["Gorilla (VLDB '15)<br/>in-memory cache: dod timestamps<br/>+ XOR floats = 1.37 B/sample"]
    P["Prometheus TSDB<br/>Gorilla chunks + head/WAL +<br/>2h immutable blocks +<br/>MemPostings label index"]
    VM["VictoriaMetrics<br/>same shapes, LSM-ier:<br/>nearest-delta2 + partitions +<br/>tagFilters cache"]
    IOX["InfluxDB 3 / IOx<br/>TSDB rebuilt on topic 28:<br/>WAL -> Arrow buffer -> Parquet<br/>on object storage, DataFusion SQL"]
    M["Monarch (VLDB '20)<br/>planet-scale: in-memory,<br/>push-based, query pushdown"]
    B["BtrDB (FAST '16)<br/>nanosecond telemetry: tree of<br/>time-partitioned AGGREGATES,<br/>queries hit precomputed summaries"]
    G -->|"chunk format"| P
    G -->|"chunk format"| VM
    P -->|"columnar + object store"| IOX
    G -->|"scale out"| M
    G -.->|"different regime:<br/>120 Hz/stream, 1.4M points/s/server"| B

2. The one-table summary

systemvalue codectime organizationlabel indexout-of-order
GorillaXOR floats, dod ts2h in-memory blocks(delegated to HBase layer)rejected
Prometheussame, xor.gohead + 2h blocks, exponential compactionMemPostings inverted indexbounded OOO window, separate buffer
VictoriaMetricsnearest-delta2 + optional lossy precisionBitsmonthly partitions of LSM partsindex_db + tagFilters cachebuffered in raw-rows shards
InfluxDB 3Parquet encodingsWAL → buffer → Parquet files by timecatalog + last/distinct cachesabsorbed by sort at persist
BtrDBdelta treetime-partitioned octree-ish tree(few, fat streams)version-annotated inserts

3. Experiments (experiments/)

cargo run --release --bin tsdb_bench

filewhatstatus
gen.rsscrape timestamps + gauge/counter/constant/random shapes, OOO arrivals, label setsPROVIDED
bits.rsMSB-first BitWriter/BitReader + sign_extendPROVIDED
baseline.rszigzag varint delta codec — the thing to beat, measured at 11.00 B/samplePROVIDED
gorilla.rsdod timestamp buckets + XOR-float codecSTUB
head.rsin-order fast path + bounded OOO window + LWW merge flushSTUB
index.rsMemPostings-style (name,value)→series inverted index + k-way intersectSTUB
bin/tsdb_bench.rsbaselines (provided) + gorilla ratios + OOO tax sweep + selector latencylanes

Contract highlights: gorilla roundtrip must be bit-exact for every f64; constant series ≤ ~2 bits/sample while full-entropy values must fail to compress (>8 B/sample — the codec wins on regularity, not magic); head rejects samples older than the OOO window and flush output is sorted so it feeds the in-order-only encoder; index intersection matches brute force and the unique-per-series label demonstrably explodes the postings map (the cardinality bomb, counted).

4. Reading guides

5. Cross-topic threads

  • Topic 4 (LSM): a TSDB is an LSM where the key is time — head=memtable, blocks=SSTs, compaction merges by time range, retention = drop the oldest level. VictoriaMetrics says “parts” and means it literally.
  • Topic 12 (columnar): IOx is the thesis that a TSDB is just a columnar store with a time-partitioned catalog — Parquet + DataFusion replace the custom chunk format.
  • Topic 23 (FTS): MemPostings IS an inverted index; label selectors are boolean term queries; high cardinality = unbounded vocabulary.
  • Topic 28 (cloud-native): InfluxDB 3’s WAL→object-store pipeline is topic 28’s landing-zone lesson applied to metrics.
  • Topic 29: the OOO window is a watermark — the same bounded-disorder-then-seal move as streaming watermarks (topic 27).

6. Capstone M30 (FalkorDB)

Temporal graph: edge/property history as (entity, attribute, ts) series; MATCH ... AT TIME t = snapshot read at t over history chunks. Design notes in notes.md §M-log.