A database whose whole job is to swallow a firehose of timestamped metrics and answer "what happened between then and now?" fast.
A general database expects rows you read, update, and delete in any order. Metrics aren't like that: you only append, timestamps only move forward, and queries are nearly always a time range over one metric.
A time-series DB stores points in time-ordered partitions, keeps recent data at full resolution, and downsamples older partitions into rollups (avg / min / max / count). A range query then reads only the partitions that overlap its window — raw if recent, rolled-up if old.
The write path is append-only: every point lands in the partition for its hour, in arrival order, which makes ingestion roughly O(1). A background downsampler turns aged partitions into compact rollup buckets and a retention policy expires the rest. The read path resolves a window to a set of partitions and picks raw versus rollup by age.
def insert(metric, ts, value):
part = partition_for(ts) # bucket by hour/day
part.append(ts, value) # ordered append, O(1)
def downsample(part): # background job on old partitions
part.rollup = aggregate(part) # {min,max,avg,count} per bucket
part.drop_raw() # reclaim disk
def range_query(metric, start, end):
rows = []
for part in select_overlapping(start, end):
rows += part.read(start, end) # raw if young, rollup if old
return rows
| Operation | Cost |
|---|---|
| Insert | O(1) amortised, sequential write |
| Range query | O(partitions + points read) |
| Old-data footprint | O(buckets) after downsample |
The trade-off: downsampling trades resolution for cheap, fast history. You can render months of trend instantly — but per-point detail beyond the raw-retention window is gone.
request_id in a tag multiplies series count and crushes the index.avgs needs count weighting, or the combined number is wrong.You ingest http.latency_ms tagged by service and region, ~50k points/second. You keep raw for 3 days, then 10-second rollups for 30 days, then 5-minute rollups for a year. An on-call engineer plotting "yesterday's latency for checkout in us-east" reads 10-second rollups — thousands of buckets — not billions of raw points, so the chart paints in well under a second.
Which tag is the dangerous one to add to a metric?