Data Engineering  /  Data Modeling

๐Ÿงฑ Data Modeling 20 guides ยท updated 2026

From ER diagrams to data mesh โ€” relational, dimensional, NoSQL, and governance-driven modeling for building data platforms people can trust.

Time-Series Data Modeling: Schemas for Metrics, IoT, and Events at Scale

Time-series data has a personality unlike anything else youโ€™ll model. It arrives relentlessly and in order, it is almost never updated after the fact, recent data is read constantly while old data is barely touched, and it accumulates until it becomes โ€” by volume โ€” the biggest dataset your company owns. Model it like ordinary transactional data and you get a table thatโ€™s unmanageable within a year. Model it with its personality in mind and even billions of points stay fast and affordable.

This post covers the schema decisions that matter: structure (tags vs. fields), the cardinality trap, partitioning, and the retention/downsampling lifecycle. It applies whether youโ€™re on a dedicated engine (TimescaleDB, InfluxDB, Prometheus), a warehouse (Snowflake, BigQuery), or plain PostgreSQL.

What Makes Time-Series Different

Four properties drive every design decision:

  1. Append-only. Sensor readings and metrics record what happened; you insert, you donโ€™t update. This frees the storage engine to optimize purely for ingest and range scans.
  2. Time-windowed reads. Nearly every query has a time predicate: โ€œlast hour,โ€ โ€œthis week vs. last week.โ€ Nobody asks for a random single reading from eight months ago.
  3. Recency bias. The last 24 hours might serve 95% of reads. Data value decays โ€” but rarely to zero, because trend analysis needs history.
  4. Volume. A modest fleet of 10,000 devices reporting every 10 seconds generates 86 million rows a day. Decisions that are harmless at web-app scale (a few extra bytes per row, an unpartitioned table) become budget line items here.

The Anatomy of a Well-Modeled Point

Every time-series record decomposes into three parts, and keeping them straight is the core skill:

measurement: device_metrics
tags: device_id=dev-4411, site=pune-plant, model=T300
fields: temperature=71.2, vibration=0.03, battery=87
time: 2026-07-06T09:15:00Z

The tag/field distinction sounds like engine trivia (it comes from InfluxDBโ€™s vocabulary) but it encodes a universal design question: what defines a series, and what varies within it? Get it wrong in either direction and you pay:

The Cardinality Trap

The most common way time-series systems die is tag cardinality explosion. The number of distinct series equals the product of distinct values across tags. 10,000 devices ร— 50 sites ร— 20 models is fine (the series count is bounded by devices anyway). But add one careless tag โ€” request_id, user_id, a container ID that changes on every deploy โ€” and you mint a new series per request or per pod. Index structures balloon, memory usage climbs, and queries crawl.

Rules of thumb:

Partition by Time โ€” Always

Whatever the platform, the physical layout that makes time-series manageable is time-based partitioning: the table is split into chunks (hourly, daily, monthly) so that:

In TimescaleDB this is automatic (create_hypertable), in vanilla PostgreSQL itโ€™s declarative partitioning, in BigQuery itโ€™s a partitioned table on the timestamp column, and in Parquet-based lakes itโ€™s directory layout (/year=2026/month=07/day=06/). Pair time partitioning with ordering or clustering on your primary tag (device, host) so โ€œthis device, this weekโ€ reads a thin, sorted slice.

-- TimescaleDB: the canonical shape
CREATE TABLE readings (
time TIMESTAMPTZ NOT NULL,
device_id TEXT NOT NULL,
temperature DOUBLE PRECISION,
battery DOUBLE PRECISION
);
SELECT create_hypertable('readings', 'time');
CREATE INDEX ON readings (device_id, time DESC);

The Lifecycle: Raw โ†’ Downsampled โ†’ Gone

Hereโ€™s the insight that separates sustainable time-series architectures from doomed ones: you donโ€™t need raw resolution forever โ€” you need it recently. Nobody debugging a January incident in December needs 10-second granularity; they need hourly trends. So mature designs run a resolution ladder:

Raw: 10s resolution

keep 14 days

5-min rollups

keep 6 months

Hourly rollups

keep 3 years

Dropped or archived

to object storage

Each rollup stores aggregates per bucket โ€” and which aggregates you keep is a modeling decision you must make up front, because you canโ€™t recompute what you didnโ€™t keep. The safe set: min, max, sum, count, and a percentile sketch if latency matters. Store sum and count rather than avg โ€” averages of averages are wrong, but sums and counts re-aggregate correctly to any coarser bucket. (This is the same โ€œstore facts, not conclusionsโ€ principle from the fundamentals post, applied at scale.)

Engines increasingly automate the ladder: TimescaleDBโ€™s continuous aggregates plus retention policies, InfluxDB tasks, Prometheus recording rules with remote long-term storage. The automation is pleasant; the decisions โ€” resolutions, retention windows, which aggregates survive โ€” remain yours.

Practical Details That Bite Later

A Design Checklist

  1. Timestamp in UTC, precision chosen deliberately.
  2. Tags = bounded identities; fields = measurements; no unbounded tag anywhere.
  3. Estimated series cardinality written down and sanity-checked.
  4. Time partitioning enabled from day one, with clustering on the main tag.
  5. Retention ladder defined: raw window, rollup resolutions, kept aggregates (sum+count, never just avg).
  6. Policy for late data and counter resets documented.

Time-series modeling is unusual in how predictable the failure modes are: cardinality explosions, unpartitioned tables, and keep-everything-raw-forever plans account for nearly all of the wreckage. Design around those three on day one, and the biggest dataset you own becomes, oddly, one of the easiest to live with.