SaaS & data, an interactive architecture walkthrough
Time-series database: how the architecture works
How a time-series database appends samples behind a write-ahead log, compresses them into immutable blocks, and answers range queries by label.
This is a learning explainer based on public engineering writing and general design practice. It is simplified, it is not the actual architecture of any company, and it may be inaccurate.
Interactive diagram
Overview
A time-series database stores numeric samples, each a timestamp and a value belonging to a series identified by a name and labels, and answers queries over time ranges. Writes are nearly always appends of the newest point, and reads mostly cover the recent past, so the storage is organised by time: recent samples live in memory behind a write-ahead log, and older ones are cut into immutable blocks.
Compression does most of the work. Timestamps are stored as delta-of-delta and values as XOR with the previous value, so a sample often takes one or two bytes. An inverted index maps each label pair to series ids so queries can find series without scanning. Series are hashed across shards and replicated three ways, older blocks move to object storage, and downsampled rollups serve long ranges. This is a simplified learning design, not any specific database's implementation.
Trade-offs and what changes at scale
The design is fast for appends and time-range reads but poor at updating or deleting individual points, which are handled through tombstones and compaction. Memory grows with the number of active series, not the number of samples, so high-cardinality labels are the usual cause of outages and are capped at ingest. Downsampling keeps long-range queries cheap but loses detail such as short spikes. At larger scale shards are rebalanced as series grow, and queries that span many shards are split by time and cached.
How it works, step by step
The walkthrough above, as text. Each step lights up the components named under it.
Metric Agents send batches of samples, each a series identified by labels plus a timestamp and a value, to the Write Gateway. Writes are almost all appends of the newest point, which is what the storage format is built around.
The Distributor hashes each series' labels to a shard and asks the Cardinality Limiter before accepting a series it has not seen. One bad label, such as a user id, can create millions of series and exhaust memory on every shard.
Samples are appended to the Write-ahead Log on three replicas, and the write succeeds once a quorum has them. A crashed node replays its log to rebuild what was in memory.
The Head Block keeps the last couple of hours of every active series in memory, grouped per series. Most queries ask about the recent past, so they are answered without touching disk.
A new series gets an id, and the Series Index records it under each label pair, such as service="api" and status="500". Queries intersect these posting lists to find matching series without scanning them all.
The Chunk Encoder stores timestamps as the change in the gap between points and each value XORed with the previous one. Regular scrape intervals and slowly changing values make most of these differences zero or tiny, so a sample takes one to two bytes instead of sixteen.
Every two hours the head is cut into an immutable block with its own chunks and index in Local Blocks. Immutable files are simple to cache, copy and delete as a whole.
The Compactor merges small blocks into larger ones and applies deletions, and the Retention Worker removes blocks older than the retention period. Deleting whole blocks is far cheaper than deleting individual samples.
Older blocks are uploaded to the Object Store, and the Downsampler writes 5-minute and 1-hour rollups of them. A one-year graph then reads a few thousand points per series instead of millions.
A dashboard range query goes to the Query Engine, which resolves label matchers through the Series Index and reuses cached results for ranges it has already computed. Aligning ranges to the query step is what makes those cached results reusable.
The engine reads the newest samples from the Head Block, older ones from local blocks and long ranges from object storage, choosing downsampled data when the range is wide. It then aggregates across shards, for example summing request rates per service.
Observability watches active series per shard, ingest rate and disk use. Active series count, more than sample rate, decides how much memory each node needs.
Sequence diagram
The same walkthrough as a UML sequence diagram, one lane per layer or group (19 components in all), left to right in the order a request reaches them, numbered by step. A loop on a lane is a step that stays inside that group. Solid arrows are synchronous calls, dashed are asynchronous.
Key components
Clientslayer
- Dashboards: range queries over series
- Metric Agents: send samples: series labels, time, value
Ingestdomain
- Cardinality Limiter: caps new series per tenant
- Distributor: hashes series to shards, replicates
- Write Gateway: auth, per-tenant rate limits
Replicaslayer
- Storage Replica 2: second copy of the shard
- Storage Replica 3: third copy of the shard
Storage Node (one shard)boundary
each shard owns a subset of series
- Chunk Encoder: delta-of-delta time, XOR values
- Compactor: merges blocks, applies deletes
- Head Block: last ~2 hours of samples in memory
- Local Blocks: immutable 2-hour blocks on disk
- Retention Worker: drops blocks past retention
- Series Index: label -> series ids (inverted index)
- Write-ahead Log: every sample before it is acknowledged
Long-term Tierdomain
- Downsampler: 5-minute and 1-hour rollups
- Object Store: older blocks (s3)
Querydomain
- Query Cache: results by step-aligned range (redis)
- Query Engine: plans, fans out, aggregates
Observability
- Observability: active series, ingest rate, disk
Sources
Public writing this explainer draws on. Read these for what the real systems actually do.
Make it yours
Copy this diagram and its walkthrough into your own workspace, then change anything: swap Postgres for DynamoDB, add a cache, rewrite the narration. Free, and it uses no AI credits.
Related walkthroughs
- Metrics and monitoring systemHow a monitoring system scrapes metrics from every service, alerts through grouped and routed notifications, and keeps years of data in object storage.
- Multi-tenant SaaS analytics pipelineHow a SaaS product collects events from many customers, keeps each tenant's data isolated, and serves fast dashboards from a columnar store.
- Log aggregation pipelineHow a log pipeline collects lines from every host, buffers them in a topic, indexes only labels, and keeps compressed text in object storage.
This is a learning explainer based on public engineering writing and general design practice. It is simplified, it is not the actual architecture of any company, and it may be inaccurate. Real systems differ in the details. Browse the library.