What makes a time-series database different, and when do you actually need one?
Metrics and events are append-only, always queried by time range, and grow forever — a general-purpose OLTP database can hold them, but it wasn't built to hold them well.
LEARN BY DOINGPut your intuition to work3 steps
What makes a time-series database different, and when do you actually need one?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#The shape of time-series data
Time-series data has a distinctive access pattern that's different from typical OLTP data, and that difference is the whole reason specialized databases exist for it:
- Write-heavy, append-only. New points arrive constantly (one per metric per interval, one per event), and they are almost never updated after being written — you don't go back and edit yesterday's CPU-usage reading.
- Time is always in the query. Nearly every query filters or groups by a time range: "CPU usage for host X over the last hour," "p99 latency per endpoint over the last 24 hours." A schema and index strategy that isn't built around that access pattern wastes effort optimizing for queries nobody runs.
- High cardinality, high volume, short useful lifespan per point. A single service emitting 50 metrics tagged by host, region, and endpoint can produce millions of distinct time series, each ticking every few seconds — and a single raw data point from six months ago is rarely queried at full resolution; you want the trend, not the exact sample.
- Old data matters less, in a specific way. Recent data needs to be fast to write and fast to query at full resolution; old data needs to take up much less space and can tolerate being pre-aggregated (rolled up) into coarser resolution, or dropped entirely.
#What a general-purpose relational database gets wrong here
Nothing stops you from putting time-series data in Postgres or MySQL — plenty of systems do, especially at moderate scale, and Postgres extensions like TimescaleDB exist precisely to bolt time-series behavior onto a relational engine rather than replace it. But naively, a few things go wrong at scale:
- A B-tree index on
(sensor_id, timestamp)grows linearly forever and never shrinks on its own; there's no built-in concept of "this data is old, compress or drop it." - Row-oriented storage means a query like "average temperature per hour across a million sensors" has to read every column of every row even though it only needs two columns — a huge amount of wasted I/O.
- Retention has to be hand-rolled: a cron job running
DELETE FROM readings WHERE ts < now() - interval '90 days', which itself generates a lot of dead tuples/WAL and needs vacuuming, on a table that's constantly being written to. - There's no native concept of downsampling — you write your own scheduled jobs to pre-compute hourly/daily rollups if you want fast dashboards over long ranges.
#What a purpose-built time-series database gives you
Systems like InfluxDB, TimescaleDB (Postgres + extension), Prometheus (for metrics specifically), and cloud offerings like Amazon Timestream or Google Cloud's Managed Service for Prometheus bake the following in as first-class features rather than patterns you build yourself:
- Columnar or column-friendly storage for the time-series case, so aggregate queries over long ranges only touch the columns and time buckets they actually need, and compress extremely well (successive readings from the same sensor are usually close in value — great for delta/run-length encoding).
- Automatic partitioning by time (chunks/shards per day or per week), so old chunks can be compressed, moved to cheaper storage, or dropped as a metadata operation instead of a row-by-row DELETE.
- Built-in downsampling/continuous aggregates — a rollup that maintains "average per hour" or "max per day" incrementally as new raw data arrives, so a dashboard querying a year of data hits pre-aggregated rows instead of scanning a year of raw points.
- Retention policies as configuration — "keep raw data 7 days, 1-minute rollups 90 days, hourly rollups forever" declared once, enforced automatically.
#When you actually need one — and when you don't
The honest answer is: not as early as the hype suggests. If your volume is a few metrics per minute per service and a modest retention window, Postgres with a sensible index and a manual rollup table will comfortably carry you for a long time, and you keep the enormous advantage of one less database technology to operate, back up, and staff for. The signals that genuinely justify moving are:
- Cardinality/volume outgrowing your OLTP database's comfortable write throughput — millions of distinct series, or write rates that start competing with your transactional workload for the same I/O and WAL bandwidth.
- You need retention/downsampling policies you're currently hand-rolling with cron jobs, and those jobs are becoming their own maintenance burden or occasionally step on production load.
- Your queries are fundamentally time-range aggregates (percentiles, rates, rolling averages) rather than point lookups or joins across many entities — this is the query shape time-series engines are optimized for, and where a relational engine's general-purpose query planner starts losing.
- You're building observability infrastructure itself (metrics, traces) rather than storing time-stamped business data — Prometheus-style systems are close to mandatory there because the entire ecosystem (Grafana, alerting, exporters) assumes that shape.