Time series databases (TSDB)

A time series database is a software system that stores and manages time-stamped data, where each entry is associated with a specific point in time.

Although it is possible to store time series data in many different database types, the design of these systems is distinct from relational databases, in which relationships between data points are implemented through referential models, rather than being implicit through timestamps. Therefore, time series databases are a type of NoSQL database.

Time series databases underpin monitoring and alerting systems, which evaluate stored series against rules in near real time. Beyond observability, the same storage model serves metrics collection, IoT and sensor telemetry, financial and market data, application and infrastructure performance data, and any workload that records how a value changes over time.

Data model

A time series is a sequence of points ordered by timestamp. Each point pairs a timestamp with one or more measured values. A series is identified by a combination of a metric name and a set of labels, sometimes called tags, that name the dimensions of the measurement. A cpu_usage series might be identified by labels such as host, datacenter, and instance.

The label set is what makes the model expressive, and also what makes it demanding. Every distinct combination of label names and values is a separate series. A label whose values are drawn from a small, bounded domain, such as a status code, has low cardinality. A label whose values are unique per request, such as a user ID or request trace ID, has high cardinality. High-cardinality labels multiply the number of series the database must track, inflating index size and memory pressure. This is the single most common way a time series deployment runs out of resources.

Storage and compression

Time series data has two properties that a general-purpose database cannot assume: writes are almost always appends at the latest timestamp, and reads sweep over contiguous time ranges. Storage layouts exploit both.

Because timestamps within a series are monotonically increasing and arrive at a roughly steady cadence, they compress extremely well. Delta-of-delta encoding stores the difference between successive timestamp deltas rather than the timestamps themselves, so a regular one-second cadence collapses to a small fixed code. Floating-point values compress well with XOR encoding, which stores only the bits that change between consecutive samples. These two techniques, described in the Gorilla paper, underpin the in-storage compression of several modern systems, and the same idea is the basis of in-memory time series engines that hold hot data in compressed form in RAM.

Because the workload is append-mostly and time-ordered, many engines lay data out in time chunks and use log-structured write paths so that high write rates do not fragment the store. Some systems are built on top of a wide-column store, such as OpenTSDB on HBase, and inherit that layer’s distribution model. Others, such as Prometheus, are distributed single-binary deployments that shard by series.

Query patterns

Reads against a time series database are dominated by range queries: give me this series, or this set of series, between two timestamps, optionally grouped by label and aggregated. Aggregation is almost always over time windows, such as average request rate per minute or maximum CPU over five-minute buckets. This pre-aggregation by time bucket is called downsampling. It is the read-side complement to compression: a query over a year of one-second samples returns a year of points unless the engine rolls them up first.

To avoid re-computing the same aggregations on every query, engines support continuous queries and recording rules that periodically evaluate an aggregation and persist the result. The pre-aggregated output is effectively a materialized view over the raw series, kept fresh on a schedule rather than on every write. This trades extra write work and storage for much cheaper reads.

Because these workloads are analytical rather than transactional, time series databases sit on the analytical side of the analytical databases divide, even when their query language is not SQL. The performance of a time series workload depends heavily on query optimization that understands time buckets and label cardinality, rather than join order.

Retention and downsampling

Time series data is unusual in that its value decays predictably with age. A one-second sample is invaluable for diagnosing an outage that happened minutes ago, and nearly worthless for understanding a year-old trend. Most systems combine a time-based retention policy with a downsampling pipeline: raw data is kept at full resolution for a short window, rolled up to coarser buckets for a longer window, and eventually dropped.

This produces a tiered storage layout, often described as hot, warm, and cold. Hot data lives in memory or fast local disk and answers live queries. Warm data sits on cheaper disk and answers historical dashboards. Cold data is offloaded to object storage or simply deleted. The retention policy is the contract that keeps storage cost bounded as the series keeps growing.

Trade-offs

Time series databases are optimized for the workload above, and pay for it elsewhere. They are write-optimized for append-mostly, time-ordered ingestion, which suits high-rate telemetry but makes them a poor fit for workloads that update or delete arbitrary records, or that need multi-statement ACID transactions across entities.

The cardinality trap cuts the other way on reads. Indexing every label value makes label-filtered queries fast, but the index grows with the number of distinct series. Indexes that are cheap for a relational workload can become the dominant memory consumer in a time series engine, which is why some systems deliberately limit which labels are indexed.

The "NoSQL" label is also looser here than elsewhere. Most time series databases are non-relational, but not all. TimescaleDB is a PostgreSQL extension that stores series in relational tables and keeps SQL as its query language, blurring the line drawn at the top of this entry. The defining trait is the workload and the time-ordered storage model, not the query language.

Implementations

Representative systems span the design space. Prometheus is a pull-based engine built for monitoring, with its own query language (PromQL) and a tightly coupled alert evaluator. InfluxDB is a purpose-built TSDB with a line-protocol write format and its own Flux/InfluxQL query languages. TimescaleDB layers time series abstractions on PostgreSQL. OpenTSDB runs on top of HBase. QuestDB pursues high ingestion throughput with a columnar, SQL-oriented engine. Older systems such as Graphite and RRDtool established the fixed-resolution, pre-aggregated model that several of these later generalized.

See also

References

  • Pelkonen et al. (2015). Gorilla: A Fast, Scalable, In-Memory Time Series Database. Proceedings of the VLDB Endowment.