Skip to content

Time-Series Data Modeling: InfluxDB and TimescaleDB Guide

DodaTech Updated 2026-06-22 8 min read

In this tutorial, you'll learn about Time. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Time-series Data Modeling is the practice of designing database schemas optimized for timestamped data points -- using specialized databases like InfluxDB and TimescaleDB that provide automatic Partitioning by time, continuous aggregation, data retention policies, and downsampling for efficient storage and querying of time-ordered data.

What You'll Learn

You will understand how to model time-series data in InfluxDB (tag-based) and TimescaleDB (hypertables), design efficient retention policies, create continuous aggregates for real-time dashboards, downsample historical data, and query time-series data for monitoring and analytics.

Why Time-Series Data Modeling Matters

Time-series data grows without bound. A single IoT sensor sending data every second generates 31 million data points per year. Durga Antivirus Pro collects 500 million threat detection events daily. Without proper time-series modeling, storage costs explode and query performance degrades rapidly.

Time-Series Learning Path

flowchart LR
  A[Database Design] --> B[Data Modeling Patterns]
  B --> C[Time-Series Data Modeling]
  C:::current
  classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
â„šī¸ Info

Prerequisites: Basic understanding of SQL and PostgreSQL. Familiarity with monitoring or IoT concepts is helpful.

InfluxDB Data Model

InfluxDB uses a tag-set-field-timestamp model optimized for write speed and time-range queries.

Concept Description Example
Measurement Table equivalent cpu_usage
Tags Indexed metadata (string only) host=web01,region=us-east
Fields Numeric values (not indexed) usage_idle=85.5,usage_user=12.3
Timestamp Nanosecond precision 2026-06-22T12:00:00Z

Schema Design

-- InfluxDB line protocol
-- measurement,tag1=value1,tag2=value2 field1=val1,field2=val2 timestamp

-- Write CPU metrics
cpu_usage,host=web01,region=us-east,core=0 usage_idle=85.5,usage_user=12.3,usage_system=2.2 1719061200000000000
cpu_usage,host=web01,region=us-east,core=1 usage_idle=92.1,usage_user=6.8,usage_system=1.1 1719061200000000000
cpu_usage,host=web02,region=eu-west,core=0 usage_idle=78.3,usage_user=18.5,usage_system=3.2 1719061200000000000

Querying

-- Average CPU idle by host over the last hour
SELECT MEAN(usage_idle) AS avg_idle
FROM cpu_usage
WHERE time >= now() - 1h
GROUP BY host;

Tag Best Practices

Do Don't
Index high-cardinality metadata Put numeric values in tags
Keep tag values under 100 bytes Store timestamps as tags
Use tags for GROUP BY columns Use tags for high-frequency changing values
Limit to 10-20 tags per measurement Use tags for the metric value itself

TimescaleDB (PostgreSQL Extension)

TimescaleDB extends PostgreSQL with hypertables -- automatically partitioned tables by time.

Creating a Hypertable

-- Enable extension
CREATE EXTENSION IF NOT EXISTS timescaledb;

-- Create regular table
CREATE TABLE cpu_metrics (
    time TIMESTAMPTZ NOT NULL,
    host VARCHAR(50) NOT NULL,
    region VARCHAR(20) NOT NULL,
    core INT NOT NULL,
    usage_idle DOUBLE PRECISION,
    usage_user DOUBLE PRECISION,
    usage_system DOUBLE PRECISION
);

-- Convert to hypertable (partitioned by time)
SELECT create_hypertable(
    'cpu_metrics',
    'time',
    chunk_time_interval => INTERVAL '1 day'
);

-- Add index on host for non-time queries
CREATE INDEX idx_cpu_host ON cpu_metrics (host, time DESC);

Efficient Inserts

-- Insert data (standard SQL)
INSERT INTO cpu_metrics (time, host, region, core, usage_idle, usage_user, usage_system)
VALUES
    (NOW(), 'web01', 'us-east', 0, 85.5, 12.3, 2.2),
    (NOW(), 'web01', 'us-east', 1, 92.1, 6.8, 1.1),
    (NOW(), 'web02', 'eu-west', 0, 78.3, 18.5, 3.2);

Time-Bucket Queries

-- Average CPU by host in 5-minute buckets
SELECT
    time_bucket('5 minutes', time) AS bucket,
    host,
    AVG(usage_idle) AS avg_idle,
    AVG(usage_user) AS avg_user
FROM cpu_metrics
WHERE time >= NOW() - INTERVAL '24 hours'
GROUP BY bucket, host
ORDER BY bucket, host;

-- Last 10 data points for a specific host
SELECT time, usage_idle, usage_user
FROM cpu_metrics
WHERE host = 'web01'
ORDER BY time DESC
LIMIT 10;

Continuous Aggregates

TimescaleDB continuous aggregates pre-compute time-bucketed data automatically.

-- Create continuous aggregate view
CREATE MATERIALIZED VIEW cpu_hourly
WITH (timescaledb.continuous) AS
SELECT
    time_bucket('1 hour', time) AS bucket,
    host,
    AVG(usage_idle) AS avg_idle,
    MAX(usage_idle) AS max_idle,
    MIN(usage_idle) AS min_idle,
    AVG(usage_user) AS avg_user
FROM cpu_metrics
GROUP BY bucket, host;

-- Add index on the aggregate
CREATE INDEX idx_cpu_hourly ON cpu_hourly (bucket DESC, host);

-- Refresh policy (every hour, look back 2 hours)
SELECT add_continuous_aggregate_policy('cpu_hourly',
    start_offset => INTERVAL '2 days',
    end_offset => INTERVAL '1 hour',
    schedule_interval => INTERVAL '1 hour'
);

Data Retention

InfluxDB Retention

-- InfluxDB v1: Retention policies
CREATE RETENTION POLICY "one_day" ON "mydb" DURATION 1d REPLICATION 1 DEFAULT;
CREATE RETENTION POLICY "one_week" ON "mydb" DURATION 7d REPLICATION 1;
CREATE RETENTION POLICY "one_year" ON "mydb" DURATION 365d REPLICATION 1;

-- InfluxDB v2: Buckets with retention
-- CLI: influx bucket create --name raw_metrics --retention 24h
-- CLI: influx bucket create --name downsampled --retention 365d

TimescaleDB Retention

-- Drop chunks older than 90 days
SELECT drop_chunks(INTERVAL '90 days', 'cpu_metrics');

-- Automated retention policy
SELECT add_retention_policy('cpu_metrics', INTERVAL '90 days');

-- Compression for older chunks (reduces storage 90%)
ALTER TABLE cpu_metrics SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'host',
    timescaledb.compress_orderby = 'time DESC'
);

SELECT add_compression_policy('cpu_metrics', INTERVAL '7 days');

Downsampling

Store high-resolution data for recent time and lower resolution for history.

import psycopg2
from datetime import datetime, timedelta

def downsample_hourly():
    conn = psycopg2.connect("dbname=metrics")
    cur = conn.cursor()

    # Aggregate raw data to hourly
    cur.execute("""
        INSERT INTO cpu_hourly (bucket, host, avg_idle, avg_user, sample_count)
        SELECT
            date_trunc('hour', time) AS bucket,
            host,
            AVG(usage_idle),
            AVG(usage_user),
            COUNT(*)
        FROM cpu_metrics
        WHERE time < NOW() - INTERVAL '7 days'
          AND time >= NOW() - INTERVAL '8 days'
        GROUP BY bucket, host
        ON CONFLICT (bucket, host) DO NOTHING;
    """)

    # Drop raw data older than 7 days
    cur.execute("SELECT drop_chunks(INTERVAL '7 days', 'cpu_metrics');")
    conn.commit()
    cur.close()
    conn.close()

Real-Time Analytics Query

-- Dashboard query: CPU, memory, and disk by region (last 15 minutes)
WITH cpu AS (
    SELECT
        time_bucket('30 seconds', time) AS bucket,
        region,
        AVG(100 - usage_idle) AS avg_cpu_pct
    FROM cpu_metrics
    WHERE time >= NOW() - INTERVAL '15 minutes'
    GROUP BY bucket, region
),
memory AS (
    SELECT
        time_bucket('30 seconds', time) AS bucket,
        region,
        AVG(used_pct) AS avg_mem_pct
    FROM memory_metrics
    WHERE time >= NOW() - INTERVAL '15 minutes'
    GROUP BY bucket, region
)
SELECT
    COALESCE(cpu.bucket, mem.bucket) AS time,
    COALESCE(cpu.region, mem.region) AS region,
    cpu.avg_cpu_pct,
    mem.avg_mem_pct
FROM cpu
FULL OUTER JOIN mem ON cpu.bucket = mem.bucket AND cpu.region = mem.region
ORDER BY time, region;

Common Time-Series Modeling Errors

1. Using Tags for High-Cardinality Values in InfluxDB

Tags are indexed in memory. Using unique IDs like user_id as tags (millions of distinct values) causes memory exhaustion. Use fields for high-cardinality values.

2. No Retention Policy

Without retention, time-series databases grow unbounded. Set retention policies from day one: high-res raw data for 7 days, downsampled for 1 year.

3. Not Using Time-Bucketing for Queries

Querying individual data points over months is slow. Use time_bucket() in TimescaleDB or GROUP BY time(1h) in InfluxDB.

4. Storing Non-Time-Series Data in Time-Series DB

User profiles, configuration, and reference data belong in a relational database. Time-series databases are optimized for append-heavy, time-ordered data.

5. Ignoring Compression

TimescaleDB compression reduces storage by 90%+ for older chunks. Enable compression for data older than a few days.

6. Not Aligning Chunk Intervals with Query Patterns

TimescaleDB default chunk interval is 7 days. If queries mostly access the last hour, use chunk_time_interval => INTERVAL '1 hour'.

7. Missing Index on Tags/Non-Time Columns

Filtering by host without an index causes sequential scans. Add indexes on frequently filtered tag columns.

Practice Questions

1. What is the difference between tags and fields in InfluxDB?

Tags are indexed metadata (string only) used for filtering and grouping. Fields are numeric values (not indexed) used for the actual metric. Tags go in the WHERE clause, fields in SELECT.

2. What is a TimescaleDB hypertable?

A PostgreSQL table automatically partitioned by time into chunks. Each chunk is a standard PostgreSQL table, enabling time-based partition pruning and efficient data management.

3. How does continuous aggregation work in TimescaleDB?

A materialized view that automatically refreshes as new data arrives. Pre-computes time-bucketed aggregates so dashboards query pre-computed results instead of raw data.

4. What is data downsampling and why is it important?

Reducing data resolution by aggregating raw points into time buckets (e.g., per-second to per-hour). Essential for managing storage costs while keeping historical data queryable.

5. Challenge: Design a time-series schema for server monitoring.

Requirements: 1000 servers, 200 metrics per server, 10-second collection interval, 90-day raw retention, 2-year downsampled retention. Answer: InfluxDB: Measurement server_metrics with tags host, region, service. Fields for each metric. Retention policy: raw data 90 days, 1-hour downsampled 2 years. TimescaleDB: Hypertable server_metrics chunked hourly. Compression after 7 days. Continuous aggregate for hourly data. Drop raw chunks after 90 days. Retention policy for 2 years of hourly aggregates.

FAQ

When should I choose InfluxDB over TimescaleDB?

InfluxDB is simpler for pure metrics and monitoring with its line protocol and built-in dashboarding (Chronograf, Grafana). TimescaleDB gives you full PostgreSQL (JOINs, ACID, tooling) and is better when time-series data must be combined with relational data.

Can I use PostgreSQL without TimescaleDB for time-series?

Yes, but manually Partitioning by time requires significant maintenance. TimescaleDB automates Partitioning, compression, and retention.

What is the best chunk interval for TimescaleDB?

Align with your query pattern. If dashboards query the last hour, use hourly chunks. If queries span days, use daily chunks. General rule: 1 million to 10 million rows per chunk.

How much storage do time-series databases save with compression?

TimescaleDB compression (gorilla + delta-delta) reduces storage by 90-95% for most time-series workloads. InfluxDB uses similar compression algorithms.

Try It Yourself

Build a time-series monitoring system:

  1. Set up TimescaleDB (via Docker) or sign up for InfluxDB Cloud
  2. Create a hypertable or measurement for CPU metrics
  3. Generate 1 million sample data points with a Python script
  4. Query 5-minute averages for the last hour
  5. Create a continuous aggregate for hourly data
  6. Set up a retention policy (raw: 7 days, hourly: 1 year)
  7. Compare query performance: raw data vs continuous aggregate

What's Next

Time-Series Databases
Data Modeling Patterns
Full-Text Search in Databases

You have learned time-series Data Modeling with InfluxDB and TimescaleDB, including hypertables, continuous aggregates, retention policies, and downsampling. Start by modeling your monitoring data as a time-series schema and setting up retention from day one.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro