TimescaleDB
01 / 02

TimescaleDB: Compression, Continuous Aggregates & Retention

TimescaleDB: Compression, Continuous Aggregates & Retention

Native Compression

ALTER TABLE sensor_data SET (
  timescaledb.compress,
  timescaledb.compress_segmentby = 'device_id'
);

-- Automatically compresses chunks older than 7 days using columnar
-- compression -- significantly reduces storage while remaining queryable
SELECT add_compression_policy('sensor_data', INTERVAL '7 days');

Continuous Aggregates

-- Incrementally maintained pre-computed aggregate -- only the
-- portions affected by new data get updated, not a full recompute
CREATE MATERIALIZED VIEW hourly_avg
WITH (timescaledb.continuous) AS
SELECT
  time_bucket('1 hour', time) AS bucket,
  device_id,
  avg(temperature) AS avg_temp
FROM sensor_data
GROUP BY bucket, device_id;

SELECT add_continuous_aggregate_policy('hourly_avg',
  start_offset => INTERVAL '3 hours',
  end_offset => INTERVAL '1 hour',
  schedule_interval => INTERVAL '1 hour'
);

Retention Policy

-- Automatically drops raw chunks older than 90 days -- avoids
-- unbounded storage growth without a manually-scheduled cleanup job
SELECT add_retention_policy('sensor_data', INTERVAL '90 days');

The Downsampling Pattern

A common combination: keep raw per-second readings for 7 days (recent detailed analysis), maintain an hourly continuous aggregate indefinitely (long-term trend queries), and drop the raw data past 7 days via a retention policy -- 'what was the average temperature last year' stays fast even after the per-second data is gone.

Filling Gaps for Charting

-- Fills missing buckets (e.g. a sensor went offline) so a chart
-- shows a complete, evenly-spaced series instead of silent gaps
SELECT
  time_bucket_gapfill('1 minute', time) AS bucket,
  avg(temperature) AS avg_temp
FROM sensor_data
WHERE time > now() - INTERVAL '1 hour'
GROUP BY bucket
ORDER BY bucket;

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free