Available: readPersistedAggregation() v1.252.0
The readPersistedAggregation() function is available from version 1.252.
Available: Rollup Support in readPersistedAggregation()
v1.263.0
Rollup Support for Persisted Aggregations is available from version 1.263.0.
The readPersistedAggregation() function
reads an existing persisted aggregation (PA) and returns its
contents as rows in a table. A PA without rollup support stores
result events, and those events are returned by the function. In
contrast, a PA with rollup support stores the state of the first
aggregator. When you read a PA with rollup support, the function
reads the stored aggregator state, performs the rollup operation
at the requested
span
and
timezone,
and then runs the remainder of the PA query on the rolled-up
first aggregator state.
Persisted aggregations allow query results to be stored and
retrieved efficiently without re-executing the full query over
raw data each time. The
readPersistedAggregation() function
provides access to those stored results as a table, optionally
filtered by time range and grouped by a specified interval.
For Persisted Aggregation with Rollup Support, the
readPersistedAggregation() function rolls
up the stored aggregation state to the
span
that you specify. With this rollup, you can query results at a
coarser time resolution than the schedule
interval. For more information, see
Persisted Aggregation with Rollup Support.
Use readPersistedAggregation() in queries
when:
Dashboard queries โ Pre-calculated metrics for dashboards that refresh frequently avoid repeated expensive aggregations. Query persisted results instead of recalculating from raw logs each time the dashboard loads.
Baseline analysis โ Compare current metrics against historical trends by querying pre-aggregated historical data. Establish baselines from long-term aggregated metrics without storing raw events.
Alert queries โ Run alerts against pre-aggregated data for faster evaluation and reduced resource consumption. Alerts trigger based on stored metrics rather than real-time aggregation.
Long-term trend analysis โ Maintain aggregated metrics over extended time periods without storing raw events. Query historical trends from stored aggregations rather than archival logs.
For more information about persisted aggregations, see Persisted Aggregations Syntax. For information about Persisted Aggregation with Rollup Support syntax, see Persisted Aggregation with Rollup Support.
Note
It is recommended to use the
readPersistedAggregation() function at
the beginning of the query. Using the function later in the
query will always discard anything before it, and only return
the content of the referenced persisted aggregation.
| Signature | readPersistedAggregation([end], [span], [start], tag, [timezone], [view]) |
| Compatible Context | Query, Live Query, Aggregate Alert, Filter Alert, Legacy Alert, Parser, Non-aggregate Query |
readPersistedAggregation()| Parameter | Type | Interpreted Type | Required | Default Value | Description |
|---|---|---|---|---|---|
end | string | string | optional[a] | Same as the primary query | The end of the time interval for reading the persisted aggregation, expressed as a time-point string. When specified, this value is validated against the start value, against the current time, and against the PA time interval. When not specified, the end time of the primary query is used. |
span | string | string | optional[a] | ย | The target span for the rollup of the aggregation. There is no default value; you must provide this parameter when reading a Persisted Aggregation with Rollup Support. The value must equal or exceed the scheduled interval of the persisted aggregation. For a Persisted Aggregation with Rollup Support, the value must be a positive multiple of the scheduled interval. Omit this parameter for persisted aggregations without rollup support. (added in 1.263) |
start | string | string | optional[a] | Same as the primary query | The start of the time interval for reading the persisted aggregation, expressed as a time-point string. When specified, this value is validated against the end value, against the current time, and against the PA time interval. When not specified, the start time of the primary query is used. |
tag[b] | string | string | required | ย | The tag that identifies the persisted aggregation to read. The tag must correspond to an existing PA in the specified or default view. |
timezone | string | string | optional[a] | ย | The timezone in which to perform the rollup. There is no default value; you must provide this parameter when reading a Persisted Aggregation with Rollup Support. Currently, only UTC is supported. See Supported Time Zones for the list of timezone values supported by LogScale. (added in 1.263) |
view | string | string | optional[a] | Same as the primary query | The view in which to locate the persisted aggregation. When not specified, the view of the primary query is used. The specified view must contain a PA matching the provided tag value. |
[a] Optional parameters use their default value unless explicitly set. | |||||
Hide omitted argument names for this function
Omitted Argument NamesThe argument name for
tagcan be omitted; the following forms of this function are equivalent:logscale SyntaxreadPersistedAggregation("value")and:
logscale SyntaxreadPersistedAggregation(tag="value")These examples show basic structure only.
readPersistedAggregation() Function Operation
readPersistedAggregation() reads events
from a repository (view) with tags that match the
tag
parameter. The function then filters the events by the
specified time range and outputs the results.
The
readPersistedAggregation()function locates the persisted aggregation identified by thetagparameter within the specified or default view.When
startandendare not specified, the function uses the same time interval as the primary query. When specified, these values are validated against each other, against the current time, and against the PA time interval.The
viewparameter identifies the view in which the PA is located. When not specified, the view of the primary query is used.The
spanparameter sets the time resolution for rollup retrieval. The specified value must be equal to or coarser than the PA schedule interval.The
timezoneparameter specifies the timezone for the rollup operation. There is no default value; you must provide this parameter when reading a Persisted Aggregation with Rollup Support. Currently, onlyUTCis supported.
Note
The readPersistedAggregation() function
is permitted only in static queries. It is not permitted in
cross-products such as sequence functions.
Persisted Aggregation with Rollup Support
Persisted Aggregation with Rollup Support is a variant of persisted aggregation. At read time, you can retrieve the aggregation at a coarser time resolution than the schedule interval provides. This flexibility is especially helpful for persisted aggregations that store metrics.
For example, you create a Persisted Aggregation with Rollup
Support that stores average incoming bytes on an hourly
schedule. From that same persisted aggregation, you can
retrieve daily, weekly, and monthly average incoming bytes. To
do so, pass 1d, 1w, or
1month as the
span
parameter. For a detailed explanation, see
readPersistedAggregation() Syntax Examples.
For a Persisted Aggregation with Rollup Support,
readPersistedAggregation() completes
these actions in order:
Reads the stored first aggregator states within the search time range.
Rolls up the first aggregator states to the span that you specify in the
spanparameter. A rollup operation merges smaller aggregation states, such as an hourly count, into larger aggregation states, such as a daily count.Runs the remainder of the query from the rolled-up aggregator.
This behavior resumes the persisted aggregation query from the first aggregator at the time resolution that you request.
readPersistedAggregation() Syntax Examples
This example demonstrates basic usage of
readPersistedAggregation(), reading all
events from a persisted aggregation by specifying a tag and
view.
In this scenario, a persisted aggregation exists with the tag
myTag in the view
myView. The query retrieves all events
stored under that tag. Only events matching the specified tag
are returned; events from other tags in the same view are
excluded.
readPersistedAggregation("myTag", view="myView")If event data looks like this:
| field1 | field2 | @timestamp |
|---|---|---|
| value1 | value2 | 1451606301001 |
| value3 | <no value> | 1451606301003 |
it would return:
| field1 | field2 | @timestamp |
|---|---|---|
| value1 | value2 | 1451606301001 |
| value3 | <no value> | 1451606301003 |
This example demonstrates filtering events using an absolute
time interval specified as Unix millisecond timestamps. Only
events with a @timestamp value in the
range [start, end) are returned.
In this scenario, the persisted aggregation contains four
events with timestamps spread across a short interval. The
query specifies an absolute
start
of 1451606301002 and an absolute
end
of 1451606301010. Events with timestamps
before
start
or at and after
end
are excluded from the result.
readPersistedAggregation("myTag", view="myView", start=1451606301002, end=1451606301010)If event data looks like this:
| field1 | @timestamp |
|---|---|
| 1 | 1451606301001 |
| 2 | 1451606301003 |
| 3 | 1451606301008 |
| 4 | 1451606301015 |
it would return:
| field1 | @timestamp |
|---|---|
| 2 | 1451606301003 |
| 3 | 1451606301008 |
This example demonstrates filtering events using relative
durations for
start
and
end.
Relative values are interpreted as offsets back from the outer
query's end time.
In this scenario, the outer query has an end time of
1451606301020. Specifying
start=17ms
and
end=3ms
translates to the absolute range [1451606301003,
1451606301017), that is, from 17 milliseconds before
the query end time up to 3 milliseconds before the query end
time. Events outside this range are excluded.
readPersistedAggregation("myTag", view="myView", start=17ms, end=3ms)If event data looks like this:
| field1 | @timestamp |
|---|---|
| 1 | 1451606301001 |
| 2 | 1451606301003 |
| 3 | 1451606301008 |
| 4 | 1451606301015 |
| 5 | 1451606301018 |
it would return:
| field1 | @timestamp |
|---|---|
| 2 | 1451606301003 |
| 3 | 1451606301008 |
| 4 | 1451606301015 |
This example demonstrates the behaviour of
readPersistedAggregation() when the
specified persisted aggregation contains no events.
When the persisted aggregation identified by tag
myTag in view myView has
no stored events, the function returns an empty result set. No
error is raised; the query simply produces no output rows.
readPersistedAggregation("myTag", view="myView")If the persisted aggregation contains no events, the query returns an empty result set with no rows.
Enriching Results with match()This example enriches the results of a persisted aggregation using a lookup file.
In this scenario, the persisted aggregation has the tag
myTag in the view
myView, and stores one row per
service and
humio_cluster_id. Because
readPersistedAggregation() emits ordinary
events, those rows can be enriched from a lookup file before
being processed further. A per-cluster cost rate is looked up
with match(), the
strict parameter is
set to false so that rows
whose cluster is absent from the file are kept,
default() supplies a fallback rate for
those rows, and the costs are then summed per service.
The lookup file search_cost_rates.csv
contains the following:
"humio_cluster_id","cost_per_cpu_hour"
"cluster-a","0.01"readPersistedAggregation("myTag", view="myView")
| match(file="search_cost_rates.csv", field=humio_cluster_id, column=humio_cluster_id, include=[cost_per_cpu_hour], strict=false)
| default(value="0.004", field=cost_per_cpu_hour)
| total_cost_usd := total_cpu_ms / 3600000 * cost_per_cpu_hour
| groupBy(service, function=sum(total_cost_usd, as=cost_usd))If event data looks like this:
| service | humio_cluster_id | total_cpu_ms | @timestamp |
|---|---|---|---|
| search | cluster-a | 7200000 | 1451606400000 |
| ingest | cluster-b | 3600000 | 1451606400000 |
it would return:
| service | cost_usd |
|---|---|
| ingest | 0.004 |
| search | 0.02 |
The search service ran for two CPU hours on
cluster-a at the rate from the file, which
is 0.01, giving 0.02.
The cluster cluster-b is not in the file,
so the ingest service is priced at the
default rate of 0.004 for its single CPU
hour. Without the
strict parameter
set to false, the
ingest result would have been dropped
instead.
This example rolls up an avg() aggregate
to a coarser span.
In this scenario, a persisted aggregation with rollup support
has the tag myTag in the view
myView, runs on an hourly schedule, and
stores avg(x). One event arrives at 00:01, in the
first hour, and three events arrive at 01:01, 01:02 and 01:03,
in the second hour. A
span
of 2h merges both hourly states into a
single result. A rollup is not a plain addition of the results
of each scheduled run: every aggregate is merged by its own
rule, so that the rolled-up result is the same as the one the
query would have produced had it been scheduled at the coarser
interval. For avg(), this means that the
rolled-up average is weighted by how many events each run saw.
It is not an average of averages.
readPersistedAggregation("myTag", view="myView", span="2h", timezone="UTC")If event data looks like this:
| x | @timestamp |
|---|---|
| 10 | 1451606460000 |
| 1 | 1451610060000 |
| 2 | 1451610120000 |
| 3 | 1451610180000 |
it would return:
| _avg | @timestamp |
|---|---|
| 4 | 1451606400000 |
Each scheduled run stored the following state, which is what a
span
of 1h would return:
"_avg","@timestamp"
"10","1451606400000"
"2","1451610000000"
The two hours hold four events with a total sum of
16, so the rolled-up average is
4. An average of the two hourly averages
would instead have returned 6.
This example rolls up a groupBy()
aggregate to a coarser span.
In this scenario, a persisted aggregation with rollup support
has the tag myTag in the view
myView, runs on a 10-minute schedule, and
stores groupBy(x, function=count()). Events
arrive at 00:01, 00:02 and 00:03 in the first run, at 00:11
and 00:12 in the second run, and at 00:21 and 00:22 in the
third run, so the value alpha appears in
the first two runs but not in the third. A
span
of 30m merges all three runs. Groups are
merged by key, and keys that only some of the runs saw are
carried through as they are.
readPersistedAggregation("myTag", view="myView", span="30m", timezone="UTC")If event data looks like this:
| x | @timestamp |
|---|---|
| alpha | 1451606460000 |
| alpha | 1451606520000 |
| beta | 1451606580000 |
| alpha | 1451607060000 |
| beta | 1451607120000 |
| beta | 1451607660000 |
| beta | 1451607720000 |
it would return:
| x | _count | @timestamp |
|---|---|---|
| alpha | 3 | 1451606400000 |
| beta | 4 | 1451606400000 |
Each scheduled run stored the following state, which is what a
span
of 10m would return:
"x","_count","@timestamp"
"alpha","2","1451606400000"
"beta","1","1451606400000"
"alpha","1","1451607000000"
"beta","1","1451607000000"
"beta","2","1451607600000"
The value alpha was not seen in the last
run, but it is still part of the rolled-up result.
This example rolls up a stats() aggregate
to a coarser span.
In this scenario, a persisted aggregation with rollup support
has the tag myTag in the view
myView, runs on a 10-minute schedule, and
stores stats([count(), sum(x)]). Events arrive at
00:01 and 00:02 in the first run, at 00:11 in the second run,
and at 00:21 and 00:22 in the third run. A
span
of 30m merges all three runs into a single
result. Each aggregate is merged by its own rule, at every
level of nesting.
readPersistedAggregation("myTag", view="myView", span="30m", timezone="UTC")If event data looks like this:
| x | @timestamp |
|---|---|
| 10 | 1451606460000 |
| 20 | 1451606520000 |
| 5 | 1451607060000 |
| 1 | 1451607660000 |
| 2 | 1451607720000 |
it would return:
| _count | _sum | @timestamp |
|---|---|---|
| 5 | 38 | 1451606400000 |
Each scheduled run stored the following state, which is what a
span
of 10m would return:
"_count","_sum","@timestamp"
"2","30","1451606400000"
"1","5","1451607000000"
"2","3","1451607600000"
The _count aggregate is merged by
addition and the _sum aggregate is merged
by addition, giving 5 and
38 respectively. Aggregates may also be
nested, as in groupBy(x, function=stats([count(),
sum(y)])) or groupBy(x, function=groupBy(y,
function=count())). Nested results are merged the same
way, and group keys must match at every level for their
results to be merged.
This example post-processes a rolled-up result in the reading query.
In this scenario, a persisted aggregation with rollup support
has the tag myTag in the view
myView, runs on a 10-minute schedule, and
stores groupBy(x, function=count()), with events
arriving at 00:01, 00:02 and 00:03 in the first run, at 00:11
and 00:12 in the second run, and at 00:21 and 00:22 in the
third run. Everything after the first aggregate is re-run from
the rolled-up result, so a persisted aggregation whose query
continues past the first aggregate is post-processed at the
requested span rather than at the schedule interval.
Post-processing can also be done in the reading query, since
readPersistedAggregation() emits ordinary
events. Here, the rolled-up groups are sorted by count in
descending order.
readPersistedAggregation("myTag", view="myView", span="30m", timezone="UTC")
| sort(_count, order=desc)If event data looks like this:
| x | @timestamp |
|---|---|
| alpha | 1451606460000 |
| alpha | 1451606520000 |
| beta | 1451606580000 |
| alpha | 1451607060000 |
| beta | 1451607120000 |
| beta | 1451607660000 |
| beta | 1451607720000 |
it would return:
| x | _count | @timestamp |
|---|---|---|
| beta | 4 | 1451606400000 |
| alpha | 3 | 1451606400000 |
The sort() function is applied to the
merged 30-minute result, not to the individual 10-minute runs.
This example reads a persisted aggregation for a time range in which no events were seen.
In this scenario, the persisted aggregation has the tag
myTag in the view
myView, runs on an hourly schedule, and
stores count(). There are no events in
the requested time range, so there is no event data. When the
persisted aggregation saw no events in the requested time
range, the query still runs on the empty state.
readPersistedAggregation("myTag", view="myView", span="1h", timezone="UTC")it would return:
| _count | @timestamp |
|---|---|
| 0 | 0 |
Note
In the absence of a bucket to derive the start time, the
system stamps the output with a
@timestamp value of
0.
This example enriches a rolled-up result with a lookup file.
In this scenario, a persisted aggregation with rollup support
has the tag myTag in the view
myView, runs on an hourly schedule, and
stores groupBy([service, humio_cluster_id],
function=sum(cpu_ms, as=total_cpu_ms)). Events arrive
at 00:01 and 00:02 in the first hour, and at 01:01 and 01:02
in the second hour. A
span
of 2h merges both hourly states before the
match() enrichment is applied. Enrichment
works on a rolled-up result too. The
match() function here runs after the
first aggregate, which is allowed in the reading query even
though it is not allowed after the first aggregate inside the
persisted aggregation itself.
The lookup file search_cost_rates.csv
contains the following:
"humio_cluster_id","cost_per_cpu_hour"
"cluster-a","0.01"readPersistedAggregation("myTag", view="myView", span="2h", timezone="UTC")
| match(file="search_cost_rates.csv", field=humio_cluster_id, column=humio_cluster_id, include=[cost_per_cpu_hour], strict=false)
| default(value="0.004", field=cost_per_cpu_hour)
| total_cost_usd := total_cpu_ms / 3600000 * cost_per_cpu_hour
| groupBy(service, function=sum(total_cost_usd, as=cost_usd))If event data looks like this:
| service | humio_cluster_id | cpu_ms | @timestamp |
|---|---|---|---|
| search | cluster-a | 3600000 | 1451606460000 |
| ingest | cluster-b | 1800000 | 1451606520000 |
| search | cluster-a | 3600000 | 1451610060000 |
| ingest | cluster-b | 1800000 | 1451610120000 |
it would return:
| service | cost_usd |
|---|---|
| ingest | 0.004 |
| search | 0.02 |
Each scheduled run stored the following state, which is what a
span
of 1h would return:
"service","humio_cluster_id","total_cpu_ms","@timestamp"
"search","cluster-a","3600000","1451606400000"
"ingest","cluster-b","1800000","1451606400000"
"search","cluster-a","3600000","1451610000000"
"ingest","cluster-b","1800000","1451610000000"
Rolled up to two hours, the search service
on cluster-a accounts for
7200000 ms and the
ingest service on
cluster-b for 3600000
ms, so the costs are the same as in the first example above.
Enriching a rolled-up result therefore produces the same
figures as enriching an equivalent pre-aggregated total.
Timezone Support for Persisted Aggregations with Rollup Support
Persisted aggregations are saved in the UTC timezone by default. Timezone conversion is not supported when reading persisted aggregations. The rollup operation therefore operates on UTC. Rolling data up to a day is equivalent to rolling up to a UTC day.
When you read a Persisted Aggregation with Rollup Support, you
must explicitly provide timezone.
Currently, only UTC is
supported.
Calendar Time for Persisted Aggregations with Rollup Support
The
span
parameter currently accepts only fixed-duration time values.
This means a day is always 24 hours, a week is always 7 days,
a month is always 30 days, and a year is always 365 days, as
described in Relative Time Syntax.
Calendar time is not supported when rolling up persisted aggregations.
readPersistedAggregation() Examples
Click next to an example below to get the full details.
Retrieve Events from a Persisted Aggregation
Reading events stored in a persisted aggregation from a specific
view using the readPersistedAggregation()
function
Query
readPersistedAggregation("myTag", view="myView")Introduction
In this example, the readPersistedAggregation()
function is used to retrieve all events stored under the tag
myTag in the view myView. Only
events matching the specified tag are returned; events from other tags
in the same view are excluded.
In this scenario, a persisted aggregation has been set up to
periodically aggregate web server access log data โ counting
requests per HTTP status code and endpoint โ and store the results
under the tag myTag in the view
myView. The
readPersistedAggregation() function is then used to
read back those stored results for further analysis or visualization
without re-running the original aggregation query.
Example incoming data might look like this:
| @timestamp | status_code | endpoint | request_count | avg_response_ms | error_rate |
|---|---|---|---|---|---|
| 2026-03-15T08:00:00Z | 200 | /api/users | 1523 | 142 | 0.00 |
| 2026-03-15T08:00:00Z | 200 | /api/products | 987 | 98 | 0.00 |
| 2026-03-15T08:00:00Z | 200 | /api/orders | 654 | 210 | 0.00 |
| 2026-03-15T08:00:00Z | 301 | /api/legacy | 45 | 12 | 0.00 |
| 2026-03-15T08:00:00Z | 400 | /api/users | 78 | 55 | 1.00 |
| 2026-03-15T08:00:00Z | 400 | /api/products | 34 | 48 | 1.00 |
| 2026-03-15T08:00:00Z | 401 | /api/orders | 112 | 33 | 1.00 |
| 2026-03-15T08:00:00Z | 403 | /api/admin | 29 | 28 | 1.00 |
| 2026-03-15T08:00:00Z | 404 | /api/users | 56 | 22 | 1.00 |
| 2026-03-15T08:00:00Z | 404 | /api/products | 41 | 19 | 1.00 |
| 2026-03-15T08:00:00Z | 500 | /api/orders | 18 | 890 | 1.00 |
| 2026-03-15T08:00:00Z | 500 | /api/users | 7 | 1240 | 1.00 |
| 2026-03-15T08:00:00Z | 503 | /api/products | 3 | 2100 | 1.00 |
| 2026-03-15T09:00:00Z | 200 | /api/users | 1789 | 138 | 0.00 |
| 2026-03-15T09:00:00Z | 200 | /api/products | 1102 | 95 | 0.00 |
| 2026-03-15T09:00:00Z | 200 | /api/orders | 723 | 205 | 0.00 |
| 2026-03-15T09:00:00Z | 301 | /api/legacy | 38 | 11 | 0.00 |
| 2026-03-15T09:00:00Z | 400 | /api/users | 91 | 52 | 1.00 |
| 2026-03-15T09:00:00Z | 401 | /api/orders | 98 | 31 | 1.00 |
| 2026-03-15T09:00:00Z | 404 | /api/products | 63 | 20 | 1.00 |
| 2026-03-15T09:00:00Z | 500 | /api/orders | 22 | 910 | 1.00 |
| 2026-03-15T09:00:00Z | 503 | /api/users | 5 | 1980 | 1.00 |
Step-by-Step
Starting with the source repository events.
- logscale
readPersistedAggregation("myTag", view="myView")The
readPersistedAggregation()function retrieves all events previously stored in the persisted aggregation identified by the tagmyTag. Theviewparameter specifies that the aggregation is read from the view namedmyView. If theviewparameter is omitted, the function defaults to reading from the current view in which the query is executed. Only events associated with the tagmyTagare returned; any other persisted aggregations stored under different tags withinmyVieware excluded from the result set. The function acts as a source in the query pipeline, meaning it replaces the normal event stream with the stored aggregation results, making it suitable as the first and only statement when the goal is simply to retrieve the persisted data. Event Result set.
Summary and Results
The query is used to read back pre-aggregated web server access log data
that was previously stored under the tag myTag in the
view myView, returning all fields and events captured
at the time the persisted aggregation was last written.
This query is useful, for example, to power dashboards or alerts that need to display aggregated HTTP traffic metrics โ such as request counts, average response times, and error rates per endpoint and status code โ without incurring the cost of re-running the full aggregation query over raw log data each time.
Sample output from the incoming example data:
| @timestamp | status_code | endpoint | request_count | avg_response_ms | error_rate |
|---|---|---|---|---|---|
| 2026-03-15T08:00:00Z | 200 | /api/users | 1523 | 142 | 0.00 |
| 2026-03-15T08:00:00Z | 200 | /api/products | 987 | 98 | 0.00 |
| 2026-03-15T08:00:00Z | 200 | /api/orders | 654 | 210 | 0.00 |
| 2026-03-15T08:00:00Z | 301 | /api/legacy | 45 | 12 | 0.00 |
| 2026-03-15T08:00:00Z | 400 | /api/users | 78 | 55 | 1.00 |
| 2026-03-15T08:00:00Z | 400 | /api/products | 34 | 48 | 1.00 |
| 2026-03-15T08:00:00Z | 401 | /api/orders | 112 | 33 | 1.00 |
| 2026-03-15T08:00:00Z | 403 | /api/admin | 29 | 28 | 1.00 |
| 2026-03-15T08:00:00Z | 404 | /api/users | 56 | 22 | 1.00 |
| 2026-03-15T08:00:00Z | 404 | /api/products | 41 | 19 | 1.00 |
| 2026-03-15T08:00:00Z | 500 | /api/orders | 18 | 890 | 1.00 |
| 2026-03-15T08:00:00Z | 500 | /api/users | 7 | 1240 | 1.00 |
| 2026-03-15T08:00:00Z | 503 | /api/products | 3 | 2100 | 1.00 |
| 2026-03-15T09:00:00Z | 200 | /api/users | 1789 | 138 | 0.00 |
| 2026-03-15T09:00:00Z | 200 | /api/products | 1102 | 95 | 0.00 |
| 2026-03-15T09:00:00Z | 200 | /api/orders | 723 | 205 | 0.00 |
| 2026-03-15T09:00:00Z | 301 | /api/legacy | 38 | 11 | 0.00 |
| 2026-03-15T09:00:00Z | 400 | /api/users | 91 | 52 | 1.00 |
| 2026-03-15T09:00:00Z | 401 | /api/orders | 98 | 31 | 1.00 |
| 2026-03-15T09:00:00Z | 404 | /api/products | 63 | 20 | 1.00 |
| 2026-03-15T09:00:00Z | 500 | /api/orders | 22 | 910 | 1.00 |
| 2026-03-15T09:00:00Z | 503 | /api/users | 5 | 1980 | 1.00 |
Note that the output is a direct reflection of the data stored in the
persisted aggregation at the time it was last written โ no
additional filtering, transformation, or re-aggregation is applied by
the readPersistedAggregation() function itself.
Note also that events from other tags that may exist within
myView are entirely absent from the results,
confirming that the tag filter is applied strictly. Note that the
error_rate field carries a value of
1.00 for all non-2xx and non-3xx status codes,
reflecting that these were pre-computed and stored as part of the
original aggregation logic, and their meaning depends entirely on how
the persisted aggregation was defined.
Roll Up Persisted Aggregation Results
Reading hourly persisted average response times rolled up into
two-hour spans using the
readPersistedAggregation() function with
avg()
Query
readPersistedAggregation("myTag", view="myView", span="2h", timezone="UTC")Introduction
In this example, the readPersistedAggregation()
function is used to read a persisted aggregation tagged
myTag in the view myView, and to
roll up its hourly results into two-hour spans.
The persisted aggregation is scheduled to run hourly and consists of this single aggregate query, which computes the average response time of the events seen in each run:
avg(responseTime)
The first scheduled run covers the hour starting at
00:00 and observes 4 events with the values
10, 8, 12 and
10, resulting in a _avg of
10. The second scheduled run covers the hour starting
at 01:00 and observes 12 events, resulting in a
_avg of 2.
Example incoming data might look like this:
| @timestamp | endpoint | responseTime |
|---|---|---|
| 1767225660000 | /api/login | 10 |
| 1767226500000 | /api/search | 8 |
| 1767227520000 | /api/search | 12 |
| 1767228420000 | /api/login | 10 |
| 1767229260000 | /api/login | 1 |
| 1767229320000 | /api/search | 2 |
| 1767229380000 | /api/search | 3 |
| 1767229800000 | /api/login | 1 |
| 1767230100000 | /api/search | 2 |
| 1767230400000 | /api/search | 3 |
| 1767230700000 | /api/login | 2 |
| 1767231000000 | /api/search | 2 |
| 1767231300000 | /api/search | 2 |
| 1767231600000 | /api/login | 2 |
| 1767231900000 | /api/search | 2 |
| 1767232200000 | /api/search | 2 |
Step-by-Step
Starting with the source repository events.
- logscale
readPersistedAggregation("myTag", view="myView", span="2h", timezone="UTC")Reads the results already computed and saved by the persisted aggregation tagged
myTag. The tag is given as the first, implicit parameter and identifies which persisted aggregation to read the results from.The
viewparameter specifies that the persisted aggregation belongs to the view namedmyView, which is the view in which the aggregation is scheduled to run.The
spanparameter requests the results in two-hour buckets. As the persisted aggregation is scheduled hourly, the values of two consecutive scheduled runs are merged into each bucket. The value ofspanmust be a multiple of the schedule interval of the persisted aggregation.The
timezoneparameter specifies the timezone in which to perform the rollup. Only UTC is currently supported. A rollup to a day withtimezoneset toUTCmeans a calendar day in UTC, not in the local timezone of the user.Because the persisted aggregation uses
avg()without theasparameter, the average is returned in the default output field named _avg. Each returned event also has a @timestamp field holding the start of the bucket that the value belongs to.The merge rule for
avg()weights each scheduled run by the number of events it observed. The run starting at00:00contributes a sum of40over 4 events and the run starting at01:00contributes a sum of24over 12 events, giving a rolled-up average of4for the two-hour bucket starting at00:00. Event Result set.
Summary and Results
The query is used to read the precomputed results of an hourly persisted aggregation and roll them up into coarser two-hour spans, returning a correctly weighted average response time per span.
This query is useful, for example, to report long-term response time trends at a lower resolution than the schedule interval of the persisted aggregation, without having to query the raw events again. Reading persisted results keeps such reporting queries cheap and allows them to cover periods that reach beyond the retention of the underlying events.
Sample output from the incoming example data:
| @timestamp | _avg |
|---|---|
| 1767225600000 | 4 |
Note that the rolled-up value is 4 and not
6. A plain average of the two scheduled run results,
10 and 2, would have returned
6, but the roll up weights each run by the number of
events it observed, so the 12 events of the second run count more than
the 4 events of the first run. Note also that the returned
@timestamp is the start of the two-hour bucket,
00:00, and that both scheduled runs fall into this
single bucket, which is why only one result event is returned.