TigerData logo
TigerData logo
  • Product

    Tiger Cloud

    Robust elastic cloud platform for startups and enterprises

    Agentic Postgres

    Postgres for Agents

    TimescaleDB

    Postgres for time-series, real-time analytics and events

  • Docs
  • Pricing

    Pricing

    Enterprise Tier

  • Developer Hub

    Changelog

    Benchmarks

    Blog

    Community

    Customer Stories

    Events

    Support

    Integrations

    Launch Hub

  • Company

    Contact us

    About

    Timescale

    Partners

    Security

    Careers

Log InTry for free
TigerData logo

Products

Time-series and Analytics AI and Vector Enterprise Plan Cloud Status Support Security Cloud Terms of Service

Learn

Documentation Blog Forum Tutorials Changelog Success Stories Time-series Database

Company

Contact Us Careers About Brand Community Code Of Conduct Events

Subscribe to the Tiger Data Newsletter

By submitting, you acknowledge Tiger Data's Privacy Policy

2026 (c) Timescale, Inc., d/b/a Tiger Data. All rights reserved.

Privacy preferences
LegalPrivacySitemap

Copy as HTML

Open in ChatGPT

Open in Claude

Open in v0

Chris Engelbert

By Chris Engelbert

4 min read

Jan 18, 2023

CloudMaterialized ViewsEngineeringAnnouncements & Releases

Table of contents

01 Materialized View vs. Continuous Aggregate02 Added Speed and Storage Savings With Hierarchical Continuous Aggregates03 Try the New Continuous Aggregates With TimescaleDB 2.9

An Incremental Materialized View on Steroids: How We Made Continuous Aggregates Even Better

A representation of continuous aggregates and a job scheduler, part of TimescaleDB 2.9
Cloud
Chris Engelbert

By Chris Engelbert

4 min read

Jan 18, 2023

Table of contents

01 Materialized View vs. Continuous Aggregate02 Added Speed and Storage Savings With Hierarchical Continuous Aggregates03 Try the New Continuous Aggregates With TimescaleDB 2.9

Copy as HTML

Open in ChatGPT

Open in Claude

Open in v0

Time-series data is often collected at a much higher granularity than is later required for display or historical storage. Having too much data never sounds like a problem until it becomes one for speed and storage reasons. A materialized view is commonly used to precalculate data for faster access. So, we roll data up (or downsample it) into lower granularity datasets (from minutes to days, for example). Typically, the process continues through multiple stages of rollups, going from seconds to minutes, days, weeks, months, etc.

In the past, these several rollups had to be generated from the actual raw dataset. This meant the raw data had to be around for as long as the largest rollup window, likely increasing your storage needs.

With the release of TimescaleDB 2.9, we solved this problem by adding support for hierarchical continuous aggregates. Simply put, continuous aggregates on top of continuous aggregates.

Materialized View vs. Continuous Aggregate

Continuous aggregates, which can be described as incremental and automatically updated materialized views, have been part of TimescaleDB for quite a while now. They are one of the most beloved features, enabling users to pre-aggregate data in the background and making it quickly available when necessary.

A common use case for continuous aggregates is dashboards, where data is often displayed at a much lower granularity than it was recorded (we covered the particular use case of real-time analytics in this blog post). Imagine a data point like CPU usage, which is recorded at a second’s granularity. It is unlikely to display it at the same granularity level in Grafana. You’d normally use averages or percentiles over the course of a minute or even lower, such as a five-minute window.

SELECT
	time_bucket('5 minutes', "time") AS "time",
	avg(cpu_usage) AS "avg_cpu_usage"
FROM cpu_usage_metrics
WHERE "time" BETWEEN now() - INTERVAL '5 days' AND now()
  AND "machine_id" = 42
GROUP BY 1
ORDER BY 1;

While you can query this time window directly from the raw data, depending on the amount of data and the ingress granularity, the query may not satisfy your response time requirements or deliver a “laggy” user experience.

Pre-calculating the required granularity helps give an instant feel to the dashboard for an amazing user experience.

CREATE MATERIALIZED VIEW cpu_usage_metrics_avg_5min
	WITH (timescaledb.continuous) AS
SELECT
	time_bucket('5 minutes', "time") AS "time",
	"machine_id",
	avg(cpu_usage) AS "avg_cpu_usage"
FROM cpu_usage_metrics
GROUP BY 1, 2
ORDER BY 1;

Querying the same result data as above is now as simple as any other query:

SELECT
	"time",
	"avg_cpu_usage"
FROM cpu_usage_metrics_avg_5min
WHERE "time" BETWEEN now() - INTERVAL '5 days' AND now()
  AND "machine_id" = 42
ORDER BY 1;

If you need to work with yet another granularity level, let’s say 15 minutes, just create another continuous aggregate with the necessary rollup window, and you’ll be fine. That is, if the raw data is available for rolling up at the internal refresh window, which can lead to issues when you need to roll up data for a monthly time window. All raw data needs to be available at that point in time.

Well, not anymore!

Added Speed and Storage Savings With Hierarchical Continuous Aggregates

With TimescaleDB 2.9 or later, you can roll up a continuous aggregate from a previous continuous aggregate. That means the FROM clause can reference another continuous aggregate, which wasn’t allowed before.

Returning to the 15-minute example, we can now implement a continuous aggregate using the already pre-aggregated five-minute one. For the sake of correctness (since it uses an average, and those can be tricky when using multi-stage averages), let’s slightly change the five-minute continuous aggregate by adding an intermediate sum and count.

CREATE MATERIALIZED VIEW cpu_usage_metrics_avg_5min
	WITH (timescaledb.continuous) AS
SELECT
	time_bucket('5 minutes', "time") AS "time",
	"machine_id",
	avg(cpu_usage)   AS "avg_cpu_usage",
	sum(cpu_usage)   AS "sum_cpu_usage",
	count(cpu_usage) AS "count_cpu_usage"
FROM cpu_usage_metrics
GROUP BY 1, 2
ORDER BY 1;

With that out of the way, the 15-minute continuous aggregate is as simple as the following:

CREATE MATERIALIZED VIEW cpu_usage_metrics_avg_15min
	WITH (timescaledb.continuous) AS
SELECT
	time_bucket('15 minutes', "time") AS "time",
	"machine_id",
	sum(sum_cpu_usage) / sum(count_cpu_usage) AS "avg_cpu_usage"
FROM cpu_usage_metrics_avg_5min
GROUP BY 1, 2
ORDER BY 1;

As you can see, we don’t use the average function anymore, but the two additional intermediate values to build the average. For other aggregations, it may be easier or more complex depending on the multi-stage aggregation requirements of the algorithm.

Anyhow, we end up with pre-aggregated 15-minute slices per machine, just as if we’d calculated it straight from the raw data. The benefit here is that you can already expire and delete the raw data after the initial five-minute window is calculated, dropping the amount of stored data to one-third. Imagine the storage savings with something like a monthly time window.

And that’s not only true for the raw data, but every single continuous aggregate can have its own retention policy, too. Just make sure the data is further aggregated before it's retired and removed.

But there is one additional benefit: speed. It’s much faster to average over three values than 900. While it doesn’t make a massive difference at this level, more complex algorithms will be a lot faster based on the number of data points.

image
Diagram example of hierarchical continuous aggregates functionality for a finance use case

As a quick side note for the term hierarchical; we called the feature hierarchical continuous aggregates since you may branch out from one continuous aggregate into many. One example would be a continuous aggregate with one-day time slices, which is then aggregated into multiple continuous aggregates, such as seven days, 14 days, one month, etc. The branching can become arbitrarily complex—the limit is your imagination.

Try the New Continuous Aggregates With TimescaleDB 2.9

Timescale is happy to release hierarchical continuous aggregates with TimescaleDB 2.9. It is one of the most requested and wished-for features, and we love to make our users (you!) happy with the functionality that is actually needed.

Like always, this is not the only cool new addition to TimescaleDB 2.9. Other features include time zone support for time_bucket_gapfill (an extension of the time zone support for time_bucket in 2.8) or fixed schedule support for background jobs. For a complete list, check out the Release Notes.

For Timescale users, upgrades are automatic, and you’ll be upgraded automatically using the next maintenance window.

If you are new to Timescale, start your free 30-day trial now, no credit card required, and get your new database journey started in five minutes.

If you’re self-hosting TimescaleDB, follow the upgrade instructions in our documentation.


About the author

Chris Engelbert

By Chris Engelbert

Related posts

Self-Hosted or Cloud Database? A Countryside Reflection on Infrastructure Choices

Self-Hosted or Cloud Database? A Countryside Reflection on Infrastructure Choices

CloudPostgreSQL

Apr 03, 2024

Read what country living can teach you about infrastructure choices and choosing a self-hosted vs. cloud database.

Read more

Introducing New Cloud Regions and How We Choose Them

Introducing New Cloud Regions and How We Choose Them

Announcements & ReleasesCloud

Mar 04, 2024

You can now create Timescale cloud services in four new regions. Keep reading to learn where and what creating them actually entails.

Read more

Stay updated with new posts and releases.

Receive the latest technical articles and release notes in your inbox.

Share

Get Started Free with Tiger CLI