Continuous aggregates in are a kind of hypertable that is refreshed automatically in
the background as new data is added, or old data is modified. Changes to your dataset are tracked,
and the hypertable behind the continuous aggregate is automatically updated in the background.
Continuous aggregates have a much lower maintenance burden than regular materialized views,
because the whole view is not created from scratch on each refresh. This means that you can get on
with working your data instead of maintaining your database.
Because continuous aggregates are based on hypertables, you can query them in exactly the same way
as your other tables. This includes continuous aggregates in the rowstore, compressed into the
columnstore, or tiered to object storage. You can even create
continuous aggregates on top of your continuous aggregates, for an even more
fine-tuned aggregation.
Real-time aggregation enables you to combine pre-aggregated data from the
materialized view with the most recent raw data. This gives you up-to-date results on every query.
Types of aggregation
There are three main ways to make aggregation easier: materialized views, continuous aggregates, and real-time aggregates.- Materialized views are a standard function. They are used to cache the result of a complex query so that you can reuse it later on. Materialized views do not update regularly, although you can manually refresh them as required.
- Continuous aggregates are a -only feature. They work in a similar way to a materialized view, but they are updated automatically in the background, as new data is added to your database. Continuous aggregates are updated continuously and incrementally, which means they are less resource intensive to maintain than materialized views. Continuous aggregates are based on hypertables, and you can query them in the same way as you do your other tables.
- Real-time aggregates are a -only feature. They are the same as continuous aggregates, but they add the most recent raw data to the previously aggregated data to provide accurate and up-to-date results, without needing to aggregate data as it is being written.
In v2.13 and later, real-time aggregates are DISABLED by default. In earlier versions, real-time
aggregates are ENABLED by default; when you create a , queries to that view include the results from the most
recent raw data.
Continuous aggregates on continuous aggregates
You can create a continuous aggregate on top of another continuous aggregate. This allows you to summarize data at different granularities. For example, you might have a raw that contains second-by-second data. Create a continuous aggregate on the to calculate hourly data. To calculate daily data, create a continuous aggregate on top of your hourly continuous aggregate. For more information, see hierarchical continuous aggregates.Continuous aggregates with JOINs
s support the following JOIN features:
JOINs in must meet the following conditions:
- Only the changes to the are tracked, and they are updated in the when it is refreshed. Changes to standard tables are not tracked.
- You can use
INNER,LEFT, andLATERALjoins; no other join type is supported. - Joins on the materialized of a are not supported.
- Hierarchical s can be created on top of a with a
JOINclause, but cannot themselves have aJOINclause.
JOIN examples
Given the following schema:compress_after in the
policy. This columnar format enables fast scanning and
aggregation, optimizing performance for analytical workloads while also saving significant storage space. In the
conversion, s are compressed by up to 98%, and organized for efficient, large-scale queries.
You can customize this policy later using alter_job. However, to change after or
created_before, the compression settings, or the the policy is acting on, you must
remove the columnstore policy and add a new one.
You can also manually convert s in a to the .
See the following JOIN examples on s:
-
INNER JOINon a single equality condition, using theONclause: -
INNER JOINon a single equality condition, using theONclause, with a further condition added in theWHEREclause: -
INNER JOINon a single equality condition specified inWHEREclause: -
INNER JOINon multiple equality conditions:v2.16.x and higher. -
INNER JOINwith a single equality condition specified inWHEREclause can be combined with further conditions in theWHEREclause:v2.16.x and higher. -
INNER JOINbetween a and multiple tables:v2.16.x and higher. -
LEFT JOINbetween a and a table:v2.16.x and higher. -
LATERAL JOINbetween a and a subquery:v2.16.x and higher.
Function support
In v2.7 and later, s support all aggregate functions. This includes both parallelizable aggregates, such asSUM and AVG, and non-parallelizable aggregates, such as RANK.
In v2.10.0 and later, the FROM clause supports JOINs, with some restrictions. For more
information, see the JOIN support section.
In older versions of , s only support aggregate functions that can be parallelized by
. You can work around this by aggregating the other parts of your query in the
, then using the window function to query the aggregate.
The following table summarizes the aggregate functions supported in continuous aggregates:
DISTINCT works in aggregate functions, not in the query definition. For example, for the table:
- The following works:
- This does not:
timescaledb.finalized
parameter to false when you create your .
Components of a continuous aggregate
s consist of:- Materialization hypertable to store the aggregated data
- Materialization engine to aggregate data from the raw, underlying table to the materialization
- Invalidation engine to determine when data needs to be re-materialized due to changes in the data
- Query engine to access the aggregated data
Materialization hypertable
s take raw data from the original , aggregate it, and store the aggregated data in a materialization . When you query the continuous aggregate view, the aggregated data is returned to you as needed. Using a temperature example, the materialization table looks like this:
The materialization table is stored as a , to take advantage of the
scaling and query optimizations that s offer. Materialization tables contain a column
for each group-by clause in the query, and an
aggregate column for each aggregate in the query.
For more information, see materialization hypertables.
Materialization engine
The materialization engine performs two transactions. The first transaction blocks all INSERTs, UPDATEs, and DELETEs, determines the time range to materialize, and updates the invalidation threshold. The second transaction unblocks other transactions, and materializes the aggregates. The first transaction is very quick, and most of the work happens during the second transaction, to ensure that the work does not interfere with other operations.Invalidation engine
Any change to the data in a could potentially invalidate some materialized rows. The invalidation engine checks to ensure that the system does not become swamped with invalidations. Fortunately, time-series data means that nearly all INSERTs and UPDATEs have a recent timestamp, so the invalidation engine does not materialize all the data, but to a set point in time called the materialization threshold. This threshold is set so that the vast majority of INSERTs contain more recent timestamps. These data points have never been materialized by the continuous aggregate, so there is no additional work needed to notify the continuous aggregate that they have been added. When the materializer next runs, it is responsible for determining how much new data can be materialized without invalidating the continuous aggregate. It then materializes the more recent data and moves the materialization threshold forward in time. This ensures that the threshold lags behind the point-in-time where data changes are common, and that most INSERTs do not require any extra writes. When data older than the invalidation threshold is changed, each transaction logs the minimum and maximum timestamps of the rows it modified. The continuous aggregate then identifies which complete time buckets are affected based on this per-transaction tracking. The range of buckets that are recalculated depends on transaction boundaries:- If you modify rows in the 10:00 bucket and rows in the 15:00 bucket within a single transaction, all buckets from 10:00 to 15:00 (including intermediate buckets 11:00, 12:00, 13:00, and 14:00) are recalculated during refresh.
- If you modify rows in the 10:00 bucket in one transaction and rows in the 15:00 bucket in a separate transaction, only the 10:00 and 15:00 buckets are recalculated. The intermediate buckets (11:00, 12:00, 13:00, 14:00) are not affected.