Rollups and query planning
Rollups pre-aggregate recurring questions so reports read summary rows instead of replaying every event. Query plans show when those summaries can answer a request.
Understand a matching rollup
A rollup groups events by a configured dimension set within a time bucket. For a source report filtered to a country, the engine needs the joint source-and-country combination.
Separate source and country totals cannot reconstruct the relationship between them.
Here is the report we want to accelerate:
var result = await analytics.QueryAsync(new AnalyticsQuery
{
Range = DateRange.LastDays(30),
GroupBy = { StandardDimensions.Source },
Filters = { Filter.Equal(StandardDimensions.Country, "US") },
Metrics = { Metric.Visitors, Metric.PageViews },
}); Add the matching joint definition to the options before opening the engine:
options.Rollups.Definitions.Add(new RollupDefinition(
StandardDimensions.Source, StandardDimensions.Country));
options.Rollups.Definitions.Add(new RollupDefinition("plan") { Events = ["purchase"] }); Configure definitions before opening the engine. Ordinary definitions added later cover new data; earlier ranges can still use retained raw events.
The first definition covers the union of the report's source grouping and country filter. The second independently prepares purchase reports grouped by plan. A definition's dimension set must match the stored dimensions needed by the query; adding unrelated dimensions is not a substitute for the required set.
Follow a report's plan
The planner considers the dimensions used by grouping, event filters, and conditional metrics, as well as the requested aggregates and historical coverage.
It can read daily summaries for aligned days, hourly or minute summaries for finer portions, and event history where aggregate coverage is unsuitable. A request can combine sources.
| Plan signal | Meaning |
|---|---|
| UsedRollups | A configured aggregate tier contributed |
| UsedRawEvents | Event-level data contributed |
| UsedDimensionIndexes | An ordered copy narrowed raw reads |
| UsedRollupIndexes | A covering equality index read aggregate rows |
| Warnings | A condition affects coverage or interpretation |
Each step names an event, a time range, and a source. Indexed steps also identify the equality dimension.
Inspect the result returned by the report:
Console.WriteLine($"Complete coverage: {result.Plan.IsComplete}");
Console.WriteLine($"Rollup index used: {result.Plan.UsedRollupIndexes}");
Console.WriteLine($"Dimension index used: {result.Plan.UsedDimensionIndexes}");
foreach (var step in result.Plan.Steps)
Console.WriteLine($"{step.Event}: {step.Source}, " +
$"{step.From:O} to {step.To:O}, index: {step.IndexDimension ?? "none"}");
foreach (var warning in result.Plan.Warnings)
Console.WriteLine($"Warning: {warning}"); UsedRawEvents also includes dimension-index reads because those still aggregate individual event payloads. UsedRollupIndexes identifies the aggregate-index path. These flags can both be true when different parts of a report use different sources.
Use suggestions for recurring queries
When automatic execution falls back because a suitable dimension set is missing, the plan can suggest a definition:
// Apply to the options used to open the next engine.
foreach (var group in result.Plan.RollupSuggestions.GroupBy(x => x.Definition.Id))
{
var index = options.Rollups.Definitions.FindIndex(x => x.Id == group.Key);
var current = index >= 0 ? options.Rollups.Definitions[index] : null;
if (current != null && current.Events == null)
continue; // This dimension set already covers every event.
var definition = new RollupDefinition(group.First().Definition.Dimensions.ToArray())
{
Events = group.Select(x => x.Event)
.Concat(current?.Events ?? [])
.Distinct(StringComparer.Ordinal)
.ToArray(),
};
if (index < 0)
options.Rollups.Definitions.Add(definition);
else
options.Rollups.Definitions[index] = definition;
} Apply a suggestion to the configuration used to open the next engine. It accelerates newly tracked activity; an ordinary added definition does not rebuild expired or existing history.
Several event metrics can suggest the same dimension set. The example merges their event scopes and updates an existing restricted definition instead of adding duplicate definitions.
Suggestions are advisory. They do not resolve every cause of raw execution, such as segments, range boundaries, or missing optional aggregate state.
Enable the aggregates your reports need
Counts and standard numeric aggregates are stored in rollups. Percentiles, first and last values, and distinct dimension counts require additional state:
options.Rollups.ValueAggregates["load_time"] = ValueAggregates.Percentiles;
options.Rollups.ValueAggregates["temperature"] = ValueAggregates.FirstAndLast;
options.Rollups.DistinctDimensions.Add("tenant"); Without the required aggregate state, the engine uses retained raw events where available. A query that needs unavailable history or disabled raw storage can produce a warning or fail, depending on the request.
Explain slow filtered reports
An unfiltered report may read a few summary rows. Adding a filter can require a different joint combination and much more event-level work.
| Report | Required stored dimension set | Selective rollup-index leader |
|---|---|---|
| Group by source | source | Usually unnecessary for this unfiltered report |
| Filter source, group by country | source + country | source |
| Filter country, group by source | source + country | country |
| Filter page, group by source | page + source | page |
The two middle reports share an aggregate combination but need different leading keys for selective equality reads. Session and goal metrics can add separate plan steps with their own requirements.
Dimension indexes narrow matching events. Rollup indexes provide an ordered aggregate path for explicitly selected equality filters. Choose the approach from the report's plan rather than its displayed row count.
Coverage also depends on activation dates, retention, and enabled time tiers. Enabling an index cannot recover expired raw history.