Skip to content

RedDB navigation

metrics

Time-series

Retention, downsampling, native window queries.

What it is

Time-series on RedDB.

Native time-series collection with retention policies, materialised views for downsampling, and time_bucket() window functions baked into the SQL grammar.

Same engine as your relational tables, so you can join an events time-series to a users table in one query — what InfluxDB → Postgres sync jobs exist to avoid.

Powers the metrics surface RedDB itself ships (KPI cards, distribution histograms) so the abstraction is battle-tested by the platform.

Code

Write it. Read it. Same engine.

Insert
INSERT INTO cpu_metrics (metric, value, tags) VALUES ('cpu.idle', 95.2, {"host":"srv1"});
Query
SELECT time_bucket('5m', timestamp) AS bucket, AVG(value)
FROM cpu_metrics
WHERE metric = 'cpu.idle'
GROUP BY bucket ORDER BY bucket DESC LIMIT 12;

Use cases

Where time-series earn their place.

  • Application metrics

    Latency p99, error rate, queue depth — everything you would otherwise pipe to Influx.

  • Product analytics

    Event streams aggregated by minute / hour / day, with retention policies that prune the raw rows automatically.

  • Billing usage

    Per-customer resource consumption with materialised monthly rollups for invoicing.

  • IoT telemetry

    High-cardinality device tags filtered and downsampled at write time.

Build it

End-to-end walkthrough.

Four steps from empty database to a real time-series workload — each step shows the exact code and explains what the engine is doing.

  1. Step 1

    1. Declare the time-series

    Retention is engine-managed — the storage drops chunks older than 90 days automatically, no cron job. CHUNK INTERVAL is the partition granularity; 1 day is sane for typical metric workloads.

    Step 1
    CREATE TIMESERIES metrics
      RETENTION 90 d
      CHUNK INTERVAL 1 d;
  2. Step 2

    2. Insert points

    Tags are JSONB so you can store arbitrary dimensions without schema changes. The engine indexes the high-cardinality metric + tags axis automatically — queries filtering by host, region, etc, are sub-linear in row count.

    Step 2
    INSERT INTO metrics (timestamp, metric, value, tags) VALUES
      (now(), 'cpu.idle',     95.2, '{"host":"srv1","region":"gru"}'),
      (now(), 'cpu.user',      3.1, '{"host":"srv1","region":"gru"}'),
      (now(), 'memory.used', 4096,  '{"host":"srv1","region":"gru"}');
  3. Step 3

    3. Aggregate with time_bucket

    time_bucket is a window function over the timestamp axis — the dashboarding workhorse. Combine with standard aggregates (AVG, percentile, MAX) and grouping by tags to get the chart-ready shape in one query.

    Step 3
    SELECT
      time_bucket('5 minutes', timestamp) AS bucket,
      tags->>'host'                      AS host,
      AVG(value)                         AS avg_idle,
      PERCENTILE_CONT(0.99)
        WITHIN GROUP (ORDER BY value)    AS p99_idle
    FROM metrics
    WHERE metric = 'cpu.idle'
      AND timestamp > now() - INTERVAL '6 hours'
    GROUP BY bucket, host
    ORDER BY bucket DESC;
  4. Step 4

    4. Materialise a continuous aggregate

    Materialised views collapse millions of points into pre-aggregated rows the engine refreshes incrementally. The dashboard hits the view, not the raw collection — KPI queries resolve in milliseconds even after months of accumulated data.

    Step 4
    CREATE MATERIALIZED VIEW mv_metrics_kpi_1d AS
      SELECT
        time_bucket('1 day', timestamp) AS bucket,
        metric,
        AVG(value) AS avg_value
      FROM metrics
      WHERE metric IN ('cpu.idle','memory.used','storage.used','connections.active')
      GROUP BY bucket, metric;
    
    -- Daily KPI dashboard reads from the view, not from raw points.
    SELECT * FROM mv_metrics_kpi_1d
    WHERE bucket > now() - INTERVAL '30 days'
    ORDER BY bucket;

Engine features

What RedDB adds on top of time-series.

The engine knobs you would otherwise wire up yourself — TTL, encryption, indexes, atomic ops, eventual consistency — applied to this data model.

  • Retention (chunk-level)

    CREATE TIMESERIES … RETENTION <duration> drops whole chunks once they age out — no per-row delete, no vacuum cycle. Storage stays bounded automatically.

  • time_bucket window functions

    time_bucket('5 minutes', timestamp) is SQL-native. Combine with standard aggregates and tag GROUP BY for chart-ready output.

  • Materialised views (incremental refresh)

    Continuous aggregates over the source series. The engine refreshes them at commit time as new chunks land — dashboards read the view, not raw points, so KPI queries resolve in milliseconds.

  • Partition TTL

    Per-chunk TTL overrides on CREATE HYPERTABLE. A noisy metric can have 7 days; a billing usage series can have 13 months. Same series, per-partition policies.

  • High-cardinality tag indexing

    Tag tuples ({host, region, service}) are indexed automatically. Queries filtering by host = ? or region = ? are sub-linear even at 10 M unique combinations.

  • Joinable to relational tables

    JOIN users u ON u.id = m.tags->>'user_id' works directly. The planner picks the cheaper side — you do not stage time-series data into a sidecar warehouse to enrich it.

  • Eventual consistency reducers

    High-throughput counter time-series (request counts, error tallies) can use EC Sum reducers — append to the txn log without a write lock, consolidate periodically. Lower latency at the cost of a read-staleness window.

  • Policies scoped by tag

    A policy can constrain reads to specific tag dimensions — Allow: tags.tenant_id = ${claim.tenant_id} is a one-line tenant isolation rule across every metric query.

  • Read replicas

    Time-series queries are read-heavy and bursty (dashboards, scheduled reports). Read replicas off-load aggregate scans from the primary; the planner routes reads when replica lag is below the configured threshold.

  • PITR via WAL archive

    Point-in-time recovery walks the WAL backwards to any wall-clock timestamp. Useful for "show the metrics state from before the bad deploy" forensics.

Showcases

What time-series can do once you bring the rest of the engine in.

Cross-model correlation, algorithm flex, multi-model patterns — things that take a stack of services elsewhere.

Per-tenant SLA monitoring

Latency time-series joined to a tenants table — alerts when any single tenant breaches.

A request-latency time-series tagged with tenant_id joins to the relational tenants table to pick up the per-tenant SLA target. Any tenant whose 5-minute p99 breaches their own SLA fires an alert — without an external observability stack to maintain.

sql
Per-tenant SLA monitoring
WITH latency_5m AS (
  SELECT
    time_bucket('5 minutes', timestamp) AS bucket,
    tags->>'tenant_id'                  AS tenant_id,
    PERCENTILE_CONT(0.99)
      WITHIN GROUP (ORDER BY value)     AS p99
  FROM metrics
  WHERE metric = 'request.latency'
    AND timestamp > now() - INTERVAL '15 minutes'
  GROUP BY bucket, tenant_id
)
SELECT t.name, l.bucket, l.p99, t.sla_p99_ms
FROM latency_5m l
JOIN tenants t ON t.id::text = l.tenant_id
WHERE l.p99 > t.sla_p99_ms
ORDER BY l.bucket DESC, l.p99 DESC;

Live billing rollup powered by a continuous aggregate

Per-customer monthly usage updated incrementally as raw points land.

A billing_usage time-series feeds a materialised view bucketed by day. The invoicing job sums daily buckets into a calendar month — never scans the raw points. The view refreshes itself at commit time as new chunks land; you don't run a refresh cron.

sql
Live billing rollup powered by a continuous aggregate
CREATE MATERIALIZED VIEW mv_billing_daily AS
  SELECT
    time_bucket('1 day', timestamp) AS day,
    tags->>'org_id'                 AS org_id,
    metric_kind,
    SUM(quantity)                   AS total
  FROM billing_usage
  GROUP BY day, org_id, metric_kind;

-- Monthly invoice query (sub-millisecond — reads from the view).
SELECT org_id, metric_kind, SUM(total) AS month_total
FROM mv_billing_daily
WHERE day >= date_trunc('month', now())
GROUP BY org_id, metric_kind;

Migrate from

Bring time-series over from your current tool.

From

InfluxDB

Export Influx line-protocol via influx_inspect, transform to RedDB INSERT statements. Tags become a JSONB column.

Migrate from InfluxDB
influx_inspect export -database telegraf -out telegraf.txt
red import --timeseries metrics --line-protocol telegraf.txt \
  reds://admin@<db>.<org>.db.reddb.io:5050

From

TimescaleDB

Hypertables map to RedDB time-series 1:1. SELECT INTO via the wire protocol — no driver change needed.

Migrate from TimescaleDB
INSERT INTO metrics (timestamp, metric, value, tags)
SELECT time, metric, value, tags::jsonb
FROM timescale_export.metrics_hypertable;

Recipes

Common patterns, real driver code.

  • Latency p99 dashboard

    sql

    Continuous aggregate over per-request latency points — 5-minute buckets the dashboard reads instead of raw rows.

    sql
    CREATE MATERIALIZED VIEW mv_request_latency_5m AS
      SELECT
        time_bucket('5 minutes', timestamp) AS bucket,
        tags->>'route' AS route,
        PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY value) AS p99_ms
      FROM metrics
      WHERE metric = 'request.latency'
      GROUP BY bucket, route;
  • Per-customer billing usage

    sql

    Monthly resource consumption rolled up at write time so the invoicing job is a single SELECT.

    sql
    SELECT
      tags->>'org_id' AS org,
      metric,
      SUM(value) AS total
    FROM billing_usage
    WHERE timestamp >= date_trunc('month', now())
      AND timestamp <  date_trunc('month', now()) + INTERVAL '1 month'
    GROUP BY org, metric
    ORDER BY org, metric;
  • IoT telemetry write path

    python

    Bulk-insert via the gRPC driver — high-cardinality tags, no schema changes per device family.

    python
    from reddb import connect
    import time
    
    db = connect("reds://admin@.../my-db:5050")
    batch = []
    for reading in sensor.stream():
        batch.append({
            "timestamp": time.time_ns() // 1_000_000,
            "metric": "temperature.celsius",
            "value": reading.value,
            "tags": {"device": reading.device_id, "site": reading.site}
        })
        if len(batch) >= 5000:
            db.timeseries.insert("metrics", batch)
            batch.clear()

Limits

What it does. What it costs.

Operational caps and durability semantics, no hand-waving.

Bucket granularity
1 millisecond → 1 year (any time_bucket interval)
Retention
Engine-managed chunk drop, no row-by-row delete
Ingest throughput
~241k points/sec on commodity hardware (gRPC)
Tag cardinality
Tested up to 10 M unique tag tuples per series
Materialised views
Incrementally refreshed at commit time

When NOT to use

Don't pick time-series for these.

Honest constraints — when another model fits better.

  • Don't use time-series for OLTP rows

    A users / orgs table is not a time-series. Time-series chunks are append-only and pruned by retention; that is the wrong shape for entities that get updated.

  • Don't store text logs as time-series

    Body fields with full event JSON belong in a document collection — text + structured indexes give you free-text search. Time-series is for numeric points.

  • Avoid `tags` with unbounded user-defined fields

    Tag cardinality drives index size. Pre-validate the set of tag keys per collection so a runaway tag does not balloon storage.

vs

How RedDB time-series compare.

  • InfluxDB

    Same time_bucket / retention model on an engine that joins to your other data. No Telegraf / sync hop.

  • TimescaleDB

    Built-in retention + materialised views, same Postgres-wire compatibility, no extension to install.

Pairs with

Time-series earns more next to these.

The data models time-series compose with — a click away.

  • Tables

    Join time-series points to the relational tables that name the entities — `users`, `services`, `tenants`. Enrichment in one query.

    relational Tables guide
  • Probabilistic

    Pipe a stream into a HyperLogLog for unique-cardinality summaries in fixed memory; pair with Count-Min for top-K.

  • Queues

    Workers emit consume telemetry into the time-series — queue depth + p99 processing time visible in your existing dashboards.

    workflows Queues guide

Questions teams ask before they pick time-series.

Explore

Every other data model on RedDB.

One engine, 9 first-class data models. Mix as needed.

  • Tables

    Postgres-wire SQL with indexes, joins, transactions.

    relational Tables guide
  • Documents

    Schemaless JSON when structure is partial — query by path.

  • Graphs

    Native edges and traversals — multi-hop without a side-car.

  • Vectors

    Auto-embed on insert, similarity search, no vector DB to sync.

  • Key-Value

    Configuration, feature flags, hot keys — millisecond reads.

    fast state Key-Value guide
  • Cache

    Tiered blob cache built into the engine: L1 memory + L2 durable.

    tiered TTL Cache guide
  • Queues

    FIFO, priority, consumer groups — durable workflow primitives.

    workflows Queues guide
  • Probabilistic

    HyperLogLog, Count-Min Sketch, Cuckoo Filter — bounded-memory analytics.

Try time-series on RedDB.

Free nano database, no credit card. Self-host under BSL 1.1 or managed Cloud — same engine, same file format.