Skip to content

feat: Add materialized view support (Beta) - #1507

Merged
kodiakhq[bot] merged 2 commits into
mainfrom
drew/backport-materialized-views
Dec 19, 2025
Merged

kodiakhq[bot] merged 2 commits into
mainfrom
drew/backport-materialized-views

Conversation

@pulpdrew

@pulpdrew pulpdrew commented Dec 19, 2025 •

Copy link
Copy Markdown
Contributor

Closes HDX-3082

Summary

This PR back-ports support for materialized views from the EE repo. Note that this feature is in Beta, and is subject to significant changes.

This feature is intended to support:

  1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source
  2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram.
  3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization.

Note to Reviewer(s)

This is a large PR, but the code has largely already been reviewed.

  • For net-new files, types, components, and utility functions, the code does not differ from the EE repo
  • Changes to the various services dashboard pages do not differ from the EE repo
  • Changes to useOffsetPaginatedQuery, useChartConfig, and DBEditTimeChart differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. This is where slightly closer review would be most valuable.

Demo

Demo: MV Configuration
Config.MVs.mov
Demo: Chart Explorer
Chart.Explorer.mov
Demo: Dashboards
Dashboards.mov

Known Limitations

This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs:

  1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range.
  2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization.

Differences in OSS vs EE Support

  • In OSS, there is a beta label on the MV configurations section
  • In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured.

Testing

To test, a couple of MVs can be created on the default otel_traces table, directly in ClickHouse:

Example MVs DDL
CREATE TABLE default.metrics_rollup_1m
(
    `Timestamp` DateTime,
    `ServiceName` LowCardinality(String),
    `SpanKind` LowCardinality(String),
    `StatusCode` LowCardinality(String),
    `count` SimpleAggregateFunction(sum, UInt64),
    `sum__Duration` SimpleAggregateFunction(sum, UInt64),
    `avg__Duration` AggregateFunction(avg, UInt64),
    `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64),
    `min__Duration` SimpleAggregateFunction(min, UInt64),
    `max__Duration` SimpleAggregateFunction(max, UInt64)
)
ENGINE = AggregatingMergeTree
PARTITION BY toDate(Timestamp)
ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName);

CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m
(
    `Timestamp` DateTime,
    `ServiceName` LowCardinality(String),
    `SpanKind` LowCardinality(String),
    `version` LowCardinality(String),
    `StatusCode` LowCardinality(String),
    `count` UInt64,
    `sum__Duration` Int64,
    `avg__Duration` AggregateFunction(avg, UInt64),
    `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64),
    `min__Duration` SimpleAggregateFunction(min, UInt64),
    `max__Duration` SimpleAggregateFunction(max, UInt64)
)
AS SELECT
    toStartOfMinute(Timestamp) AS Timestamp,
    ServiceName,
    SpanKind,
    StatusCode,
    count() AS count,
    sum(Duration) AS sum__Duration,
    avgState(Duration) AS avg__Duration,
    quantileTDigestState(0.5)(Duration) AS quantile__Duration,
    minSimpleState(Duration) AS min__Duration,
    maxSimpleState(Duration) AS max__Duration
FROM default.otel_traces
GROUP BY
    Timestamp,
    ServiceName,
    SpanKind,
    StatusCode;
CREATE TABLE default.span_kind_rollup_1m
(
    `Timestamp` DateTime,
    `ServiceName` LowCardinality(String),
    `SpanKind` LowCardinality(String),
    `histogram__Duration` AggregateFunction(histogram(20), UInt64)
)
ENGINE = AggregatingMergeTree
PARTITION BY toDate(Timestamp)
ORDER BY (Timestamp, ServiceName, SpanKind);

CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m
(
    `Timestamp` DateTime,
    `ServiceName` LowCardinality(String),
    `SpanKind` LowCardinality(String),
    `histogram__Duration` AggregateFunction(histogram(20), UInt64)
)
AS SELECT
    toStartOfMinute(Timestamp) AS Timestamp,
    ServiceName,
    SpanKind,
    histogramState(20)(Duration) AS histogram__Duration
FROM default.otel_traces
GROUP BY
    Timestamp,
    ServiceName,
    SpanKind;

Then you'll need to configure the materialized views in your source settings:

Source Configuration (should auto-infer when MVs are selected) Screenshot 2025-12-19 at 10 26 54 AM

@vercel

vercel Bot commented Dec 19, 2025 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
hyperdx-v2-oss-app Ready Ready Preview, Comment Dec 19, 2025 4:16pm

@changeset-bot

changeset-bot Bot commented Dec 19, 2025 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 13a740e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/common-utils Minor
@hyperdx/api Minor
@hyperdx/app Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Dec 19, 2025 •

Copy link
Copy Markdown
Contributor

E2E Test Results

✅ All tests passed • 46 passed • 3 skipped • 722s

Status Count
✅ Passed 46
❌ Failed 0
⚠️ Flaky 1
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

@claude

claude Bot commented Dec 19, 2025 •

Copy link
Copy Markdown

Code Review

Critical Issues

  • 🔒 SQL Injection vulnerability in ServiceDashboardEndpointSidePanel.tsx:50,56 → User-controlled endpoint and service values are directly interpolated into SQL conditions without escaping. Use parameterized queries or proper escaping.

Important Issues

  • ⚠️ Missing error handling in useChartConfig.tsx:276-285 → tryOptimizeConfigWithMaterializedView can throw but no try-catch wraps it. Wrap in try-catch or ensure errors propagate correctly.

  • ⚠️ Race condition potential in useChartConfig.tsx:287-323 → Query cache updates during async iteration may cause stale data if query key changes mid-flight. Consider abort signal checks after each cache update.

  • ⚠️ Inconsistent expression usage in ServiceDashboardEndpointSidePanel.tsx:50 → Changed from expressions.spanName to expressions.endpoint (line 50) but other files still use expressions.spanName for endpoint filtering. Verify this is intentional across the codebase.

  • ⚠️ Missing validation in materializedViews.ts:173-177 → Custom count() expressions are detected but error thrown may be unclear to users. Consider more actionable error message with examples.

Notes

  • ✅ Good test coverage for MV optimization logic (1603 lines of tests)
  • ✅ Proper use of structuredClone to avoid reference bugs
  • ✅ Beta labeling is appropriate for this feature
  • ✅ Type safety maintained throughout with Zod schemas

Priority: Fix the SQL injection issue before merge.

@pulpdrew
pulpdrew force-pushed the drew/backport-materialized-views branch from bb52b86 to d411308 Compare December 19, 2025 15:20
@pulpdrew
pulpdrew marked this pull request as ready for review December 19, 2025 15:53
@pulpdrew
pulpdrew requested review from a team and dhable and removed request for a team December 19, 2025 15:57
@kodiakhq
kodiakhq Bot merged commit a5a04aa into main Dec 19, 2025
12 checks passed
@kodiakhq
kodiakhq Bot deleted the drew/backport-materialized-views branch December 19, 2025 16:17
knudtty pushed a commit that referenced this pull request Apr 16, 2026
Closes HDX-3082

# Summary

This PR back-ports support for materialized views from the EE repo. Note that this feature is in **Beta**, and is subject to significant changes.

This feature is intended to support:

1. Configuring AggregatingMergeTree (or SummingMergeTree) Materialized Views which are associated with a Source
2. Automatically selecting and querying an associated materialized view when a query supports it, in Chart Explorer, Custom Dashboards, the Services Dashboard, and the Search Page Histogram.
3. A UX for understanding what materialized views are available for a source, and whether (and why) it is or is not being used for a particular visualization.

## Note to Reviewer(s)

This is a large PR, but the code has largely already been reviewed.

- For net-new files, types, components, and utility functions, the code does not differ from the EE repo
- Changes to the various services dashboard pages do not differ from the EE repo
- Changes to `useOffsetPaginatedQuery`, `useChartConfig`, and `DBEditTimeChart` differ slightly due to unrelated (to MVs) drift between this repo and the EE repo, and due to the lack of feature toggles in this repo. **This is where slightly closer review would be most valuable.**

## Demo

<details>
<summary>Demo: MV Configuration</summary>

https://github.com/user-attachments/assets/fedf3bcf-892c-4b8d-a788-7e231e23bcc3
</details>

<details>
<summary>Demo: Chart Explorer</summary>

https://github.com/user-attachments/assets/fc8d1efa-7edc-42fc-98f0-75431cc056b8
</details>

<details>
<summary>Demo: Dashboards</summary>

https://github.com/user-attachments/assets/f3cb247e-711f-4d90-95b8-cf977e94f065
</details>

## Known Limitations

This feature is in Beta due to the following known limitations, which will be addressed in subsequent PRs:

1. Visualization start and end time, when not aligned with the granularity of MVs, will result in statistics based on the MV "time buckets" which fall inside the date range. This may not align exactly with the source table data which is in the selected date range.
2. Alerts do not make use of MVs, even if the associated visualization does. Due to (1), this means that alert values may not exactly match the values shown in the associated visualization.

## Differences in OSS vs EE Support

 - In OSS, there is a beta label on the MV configurations section
 - In EE there are feature toggles to enable MV support, in OSS the feature is enabled for all teams, but will only run for sources with MVs configured.

## Testing

To test, a couple of MVs can be created on the default `otel_traces` table, directly in ClickHouse:

<details>

<summary>Example MVs DDL</summary>

```sql
CREATE TABLE default.metrics_rollup_1m
(
    `Timestamp` DateTime,
    `ServiceName` LowCardinality(String),
    `SpanKind` LowCardinality(String),
    `StatusCode` LowCardinality(String),
    `count` SimpleAggregateFunction(sum, UInt64),
    `sum__Duration` SimpleAggregateFunction(sum, UInt64),
    `avg__Duration` AggregateFunction(avg, UInt64),
    `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64),
    `min__Duration` SimpleAggregateFunction(min, UInt64),
    `max__Duration` SimpleAggregateFunction(max, UInt64)
)
ENGINE = AggregatingMergeTree
PARTITION BY toDate(Timestamp)
ORDER BY (Timestamp, StatusCode, SpanKind, ServiceName);

CREATE MATERIALIZED VIEW default.metrics_rollup_1m_mv TO default.metrics_rollup_1m
(
    `Timestamp` DateTime,
    `ServiceName` LowCardinality(String),
    `SpanKind` LowCardinality(String),
    `version` LowCardinality(String),
    `StatusCode` LowCardinality(String),
    `count` UInt64,
    `sum__Duration` Int64,
    `avg__Duration` AggregateFunction(avg, UInt64),
    `quantile__Duration` AggregateFunction(quantileTDigest(0.5), UInt64),
    `min__Duration` SimpleAggregateFunction(min, UInt64),
    `max__Duration` SimpleAggregateFunction(max, UInt64)
)
AS SELECT
    toStartOfMinute(Timestamp) AS Timestamp,
    ServiceName,
    SpanKind,
    StatusCode,
    count() AS count,
    sum(Duration) AS sum__Duration,
    avgState(Duration) AS avg__Duration,
    quantileTDigestState(0.5)(Duration) AS quantile__Duration,
    minSimpleState(Duration) AS min__Duration,
    maxSimpleState(Duration) AS max__Duration
FROM default.otel_traces
GROUP BY
    Timestamp,
    ServiceName,
    SpanKind,
    StatusCode;
```

```sql
CREATE TABLE default.span_kind_rollup_1m
(
    `Timestamp` DateTime,
    `ServiceName` LowCardinality(String),
    `SpanKind` LowCardinality(String),
    `histogram__Duration` AggregateFunction(histogram(20), UInt64)
)
ENGINE = AggregatingMergeTree
PARTITION BY toDate(Timestamp)
ORDER BY (Timestamp, ServiceName, SpanKind);

CREATE MATERIALIZED VIEW default.span_kind_rollup_1m_mv TO default.span_kind_rollup_1m
(
    `Timestamp` DateTime,
    `ServiceName` LowCardinality(String),
    `SpanKind` LowCardinality(String),
    `histogram__Duration` AggregateFunction(histogram(20), UInt64)
)
AS SELECT
    toStartOfMinute(Timestamp) AS Timestamp,
    ServiceName,
    SpanKind,
    histogramState(20)(Duration) AS histogram__Duration
FROM default.otel_traces
GROUP BY
    Timestamp,
    ServiceName,
    SpanKind;
```
</details>

Then you'll need to configure the materialized views in your source settings:

<details>
<summary>Source Configuration (should auto-infer when MVs are selected)</summary>

<img width="949" height="1011" alt="Screenshot 2025-12-19 at 10 26 54 AM" src="https://github.com/user-attachments/assets/fc46a1b9-de8b-4b95-a8ef-ba5fee905685" />

</details>

This branch was successfully deployed

1 active deployment
Preview — 13a740ec Deployed Dec 19, 2025 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants