Cliff Resnick
04/06/2026, 9:21 PMtimeslice(event_timestamp,interval 1 minute) as minute timestamp and is partitioned by date_trunc('hour', minute_timestamp). I added a hierarchal MV that rolls minute_timestamp data to hour_timestamp. I am partitioning this MV by date_trunc('day', hour_timestamp) and it runs every two minutes. My question: is it more ingestion time work for Starrocks to maintain the day partitioning by day rather than by hour?Rocky
04/06/2026, 9:21 PMday partitioning compared to hour partitioning in your specific setup.
Here is the breakdown of why this happens and how it affects StarRocks:
1. How StarRocks Refreshes Partitioned MVs
StarRocks uses a partition-level incremental refresh strategy for asynchronous MVs. When data in the base table (or base MV) changes, StarRocks:
* Identifies which partitions in the source have new data.
* Maps those source partitions to the corresponding partitions in the target MV.
* Recalculates and overwrites the entire target partition.
2. The Granularity Mismatch Problem
In your current setup:
* mv_minute is partitioned by Hour.
* mv_hour is partitioned by Day and refreshes every 2 minutes.
Because your refresh interval is so frequent (2 minutes), every time mv_hour runs, it sees that the "current hour" partition in mv_minute has new data. It then maps that hour to the "current day" partition in mv_hour.
The Workload Comparison:
* Partitioned by Hour: Every 2 minutes, StarRocks only needs to re-aggregate and overwrite 1 hour of data (60 rows from mv_minute). This is a very "cheap" and fast operation.
* Partitioned by Day: Every 2 minutes, StarRocks must re-aggregate and overwrite the entire current day (up to 1,440 rows from mv_minute).
* At 8:00 AM, it's re-processing 8 hours of data.
* At 11:00 PM, it's re-processing 23 hours of data.
* By the end of the day, you are essentially asking StarRocks to aggregate 24 hours of data every 2 minutes just to add the latest 2 minutes of results.
3. Resource Impact
* CPU/Memory: As the day progresses, the refresh task for mv_hour will become increasingly "heavier" and take longer to complete because the volume of data in a "day" partition grows.
* I/O (Write Amplification): You are repeatedly overwriting the same day-long partition thousands of times a day. This causes unnecessary disk I/O and pressure on the storage backend.
* Locking/Concurrency: While StarRocks handles MV refreshes efficiently, very long-running or resource-intensive refreshes occurring every 2 minutes can eventually lead to task queuing or resource contention.
Recommendation
Since you are refreshing every 2 minutes, you should partition mv_hour by Hour as well.
Why?
1. Efficiency: Each refresh will only process the current hour's data (60 minutes of source data max).
2. Performance: The refresh tasks will remain fast and constant in duration throughout the day.
3. Flexibility: You can still query by day easily (StarRocks handles the aggregation of 24 hourly partitions into a day view instantly during query time).
4. Partition Pruning: Having hourly partitions allows the query optimizer to be more precise when you search for specific time ranges (e.g., "the last 3 hours").
When would Day partitioning make sense?
Only if your refresh frequency was much lower (e.g., you only refreshed mv_hour once or twice a day) or if your data volume per hour was so tiny that having many small partitions created a "too many tablets" problem. However, for a 2-minute rollup, Hourly is the standard best practice.
References
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.md
• using_starrocks/async_mv/use_cases/data_lake_query_acceleration_with_materialized_views.mdCliff Resnick
04/06/2026, 10:35 PMtimeslice(event_timestamp,interval 1 minute) as minute timestamp and is partitioned by date_trunc('hour', minute_timestamp). I added a hierarchal MV that rolls minute_timestamp data to hour_timestamp. I am partitioning this MV by date_trunc('hour', hour_timestamp) and it runs every hour. My question is about query rewrite. If I set the minute MV to "loose" consistency, and the hour MV to "checked", I see that both MVs are used to create a fast and near-time result. But if I do a grouping by date_trunc('day', event_timestamp), it falls back to base table. How can I use the MVs in group by queries?Rocky
04/06/2026, 10:35 PMtimeslice and date_trunc are mixed in a nested hierarchy.
Why it’s falling back to the base table
When you query GROUP BY date_trunc('day', event_timestamp) on the base table, the StarRocks optimizer tries to find an MV that can satisfy this. For an MV to be a candidate for "Rollup Rewrite" (aggregating a finer-grained MV to a coarser query), the optimizer needs to prove that the query's expression can be derived from the MV's columns.
In your current hierarchy:
1. mv_minute: Uses timeslice(event_timestamp, interval 1 minute).
2. mv_hour: Is built on mv_minute and uses date_trunc('hour', minute_timestamp).
The chain the optimizer sees is: date_trunc('day', event_timestamp) (Query) $\rightarrow$ date_trunc('hour', timeslice(event_timestamp, 1m)) (MV).
While mathematically equivalent, the StarRocks rewrite engine (especially in versions prior to 3.3/3.4) is often more successful when the functions are consistent. Specifically, timeslice is often treated as a custom interval, whereas the optimizer has hardcoded logic to understand that date_trunc('hour', ...) can be rolled up into date_trunc('day', ...).
How to fix it
1. Use date_trunc consistently
If you don't strictly need the unique behavior of timeslice (which allows intervals like 5 or 15 minutes), replace it with date_trunc('minute', ...) in your first MV. StarRocks recognizes the hierarchy of date_trunc levels (minute $\rightarrow$ hour $\rightarrow$ day) much more reliably than it does with timeslice.
* Change mv_minute to: date_trunc('minute', event_timestamp) as minute_timestamp
* Result: The optimizer can now follow the logic: minute $\rightarrow$ hour $\rightarrow$ day using standard time-hierarchy rules.
2. Avoid "Hidden" Nested Columns in the Query
For nested MVs, the rewrite engine sometimes struggles to "see through" multiple layers to the base table column if the expressions change. To help the optimizer, ensure that:
* The system variable enable_query_rewrite_for_complex_expression is set to true (available in newer 3.x versions).
* The intermediate MV (mv_hour) also includes the original column name as an alias if it's just a pass-through, but since these are rollups, that's not possible. The best bet is consistent function usage.
3. Verify Aggregation Function Compatibility
Ensure you are using "roll-upable" aggregate functions. If you use count(distinct ...) in the MVs, the rewrite engine will only work for day if you used bitmap_union in the MVs. For sum, min, max, it should work automatically.
4. Query Rewrite Debugging
To see exactly why the "day" query is failing, you can run:
sql
SET [SESSION] trace_log_level = "TRACE";
EXPLAIN REWRITE SELECT date_trunc('day', event_timestamp), sum(...) FROM base_table GROUP BY 1;
Look for the MaterializedView section in the output. It will often list the reason for rejection (e.g., "expression mismatch" or "cannot find rollup path").
Summary of Recommendation
The "minute/hour" queries likely work because they match the column definitions in your MVs exactly. The "day" query fails because it requires a Rollup Rewrite, and the optimizer is getting "lost" in the translation between timeslice and date_trunc.
The most robust fix: Change mv_minute to use date_trunc('minute', event_timestamp). This aligns the entire hierarchy under the same function family, allowing the optimizer to use its built-in time-rollup logic.
References
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.mdRocky
04/06/2026, 10:35 PMCliff Resnick
04/06/2026, 10:42 PMdate_trunc('minute', event_timestamp) as minute_timestamp in the minute MV and PARTITION BY date_trunc('hour',minute_timestamp) but I get the error " Materialized view partition function date_trunc must related with column."Rocky
04/06/2026, 10:43 PMdate_trunc in the PARTITION BY clause, it expects the inner column to be a direct projection (or a simple alias) of the base table's partition column.
When you changed your SELECT to date_trunc('minute', event_timestamp) AS minute_timestamp, you created a "double truncation" in the eyes of the partition evaluator: date_trunc('hour', date_trunc('minute', event_timestamp)). Currently, the StarRocks partition binder doesn't support nested date_trunc expressions for identifying partition alignment.
The Solution
To fix the error and still enable query rewrite for day groupings, you should use the following pattern. The key is to keep the minute_timestamp name but ensure the partition clause can "see" the connection to the base table.
1. The Correct MV Definition
You should reference the base table's partition column directly in the PARTITION BY clause, and ensure it is the exact same column name used in your GROUP BY and SELECT list.
sql
CREATE MATERIALIZED VIEW mv_minute
REFRESH ASYNC
-- Use the base table's partition column here
PARTITION BY date_trunc('hour', event_timestamp)
AS
SELECT
-- Alias the result so it matches the base column name
-- OR use a simple alias that the optimizer can trace.
date_trunc('minute', event_timestamp) AS event_timestamp,
count(*) as cnt,
sum(revenue) as rev
FROM base_table
GROUP BY 1;
Why this works:
* Partitioning: By using PARTITION BY date_trunc('hour', event_timestamp), StarRocks sees a direct link to the base table's event_timestamp partition key.
* Rewrite: By using the same name (event_timestamp) or a very clear alias for the truncated column in the SELECT list, the optimizer can now map a query's date_trunc('day', event_timestamp) directly to the MV's event_timestamp column.
2. If you want to keep the alias minute_timestamp
If you prefer the alias minute_timestamp, you must use this specific syntax which is supported for "customized time granularity":
sql
CREATE MATERIALIZED VIEW mv_minute
REFRESH ASYNC
PARTITION BY date_trunc('hour', minute_timestamp)
AS
SELECT
-- Use time_slice here instead of date_trunc('minute', ...)
-- StarRocks treats time_slice as a special "base" for further truncation
time_slice(event_timestamp, INTERVAL 1 MINUTE) AS minute_timestamp,
sum(data) as total_data
FROM base_table
GROUP BY 1;
Wait, you mentioned timeslice didn't rewrite to day. This is because timeslice is more flexible than date_trunc and the optimizer is more conservative with it.
Best Recommendation for Query Rewrite
If your goal is to have GROUP BY date_trunc('day', event_timestamp) rewrite successfully to your MVs, you should aim for "Name Alignment".
1. Define mv_minute using date_trunc('minute', event_timestamp) AS event_timestamp.
2. Define mv_hour using date_trunc('hour', event_timestamp) AS event_timestamp.
3. Partition both using date_trunc('hour', event_timestamp) and date_trunc('day', event_timestamp) respectively.
When the column name in the MV matches the column name in the query (event_timestamp), the StarRocks optimizer's "Expression Mapper" has a much easier time realizing that date_trunc('day', event_timestamp) on the base table is simply a further truncation of the event_timestamp column already present in your MV.
Summary of why the error happened:
* PARTITION BY date_trunc(..., alias) works if alias is a direct column or a time_slice.
* PARTITION BY date_trunc(..., alias) fails if alias is already a date_trunc expression.
* Fix: Use the base column name in the PARTITION BY clause or ensure the alias in the SELECT list is treated as the primary time dimension for that MV.
ReferencesRocky
04/06/2026, 10:43 PMCliff Resnick
04/06/2026, 10:54 PMtime_slice works here, but not with dat_trunc('day',event)_timestamp) query rewrite.
CREATE MATERIALIZED VIEW mv_minute
REFRESH ASYNC
-- Use the base table's partition column here
PARTITION BY date_trunc('hour', event_timestamp)
AS
SELECT
-- Alias the result so it matches the base column name
-- OR use a simple alias that the optimizer can trace.
date_trunc('minute', event_timestamp) AS event_timestamp,
count(*) as cnt
FROM my_table
GROUP BY 1;Rocky
04/06/2026, 10:56 PM"Materialized view partition function date_trunc must related with column" occurs because of a conflict in how StarRocks binds the PARTITION BY clause to the SELECT list.
When you define an alias in the SELECT list that has the same name as a base table column (e.g., AS event_timestamp), the PARTITION BY clause tries to bind to your alias rather than the base column. Since your alias is already a date_trunc expression, StarRocks sees date_trunc('hour', date_trunc('minute', ...)). This "nested function" is not supported for partition tracking, hence the error.
To solve this and ensure that grouping by day still rewrites correctly, follow these steps:
1. The Fix for the Partition Error
You must ensure the PARTITION BY clause references a "clean" column. You have two options:
Option A: Use time_slice (Recommended for Partitioning)
StarRocks treats time_slice as a special "base" function that is allowed to be wrapped by date_trunc in the partition clause.
sql
CREATE MATERIALIZED VIEW mv_minute
REFRESH ASYNC
PARTITION BY date_trunc('hour', minute_timestamp) -- This is allowed on time_slice aliases
AS
SELECT
time_slice(event_timestamp, INTERVAL 1 MINUTE) AS minute_timestamp,
count(*) as cnt
FROM my_table
GROUP BY 1;
Option B: Use a Different Alias for the Rollup
If you want to use date_trunc in the SELECT list, you must use the original base table column in the PARTITION BY clause and ensure the alias in the SELECT list has a different name so there is no ambiguity.
sql
CREATE MATERIALIZED VIEW mv_minute
REFRESH ASYNC
PARTITION BY date_trunc('hour', event_timestamp) -- Binds to the base table column
AS
SELECT
date_trunc('minute', event_timestamp) AS minute_timestamp, -- Different name
count(*) as cnt
FROM my_table
GROUP BY 1;
2. The Fix for "Day" Query Rewrite
The reason your grouping by day falls back to the base table is that the optimizer struggles to map date_trunc('day', event_timestamp) to an MV that uses a different function (time_slice) or a different alias name.
In StarRocks 3.3 and 4.0.x, you can fix this by aligning your hierarchical MVs using the same alias name for the time dimension across all layers. To bypass the "partition function" error while doing this, use this specific pattern:
Step 1: Define mv_minute
Use time_slice but alias it back to the original column name. This tells the optimizer "this column is the new version of event_timestamp."
sql
CREATE MATERIALIZED VIEW mv_minute
REFRESH ASYNC
PARTITION BY date_trunc('hour', event_timestamp) -- Binder allows this with time_slice
AS
SELECT
time_slice(event_timestamp, INTERVAL 1 MINUTE) AS event_timestamp,
count(*) as cnt
FROM my_table
GROUP BY 1;
Step 2: Define mv_hour
Since mv_minute now has a column named event_timestamp (which is already truncated to the minute), mv_hour can just truncate it further.
sql
CREATE MATERIALIZED VIEW mv_hour
REFRESH ASYNC
PARTITION BY date_trunc('day', event_timestamp)
AS
SELECT
date_trunc('hour', event_timestamp) AS event_timestamp,
sum(cnt) as cnt
FROM mv_minute
GROUP BY 1;
Step 3: Enable Time-Series Rollup Rewrite
Ensure these session variables are set (they are often on by default in 4.0, but worth checking):
sql
SET enable_query_rewrite_for_complex_expression = true;
Summary of why this works:
1. Partitioning: By using time_slice in the SELECT list and aliasing it to event_timestamp, StarRocks allows the PARTITION BY date_trunc('hour', event_timestamp) syntax.
2. Rewrite: Because the column is named event_timestamp in both MVs, the optimizer’s AggregateTimeSeriesRule can easily prove that date_trunc('day', event_timestamp) from your query can be satisfied by rolling up the event_timestamp column in mv_hour.Rocky
04/06/2026, 10:56 PMEXPLAIN REWRITE SELECT ... and look for the "Reject Reason." If it mentions "Consistency," ensure mv_hour is also set to PROPERTIES("query_rewrite_consistency" = "loose").
References
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.md
• using_starrocks/async_mv/use_cases/data_modeling_with_materialized_views.md
• table_design/data_distribution/expression_partitioning.md
• using_starrocks/async_mv/feature-support-asynchronous-materialized-views.mdCliff Resnick
04/06/2026, 11:20 PMenable_query_rewrite_for_complex_expression is an unknown variable, at least in 4.0.8. There is also no such thing as explain rewrite . Finally using "`time_slice` in the SELECT list and aliasing it to event_timestamp" did not help and date_trunc('day') still falls back to base tableRocky
04/06/2026, 11:21 PM"Materialized view partition function date_trunc must related with column" occurs because you are shadowing the base table's column name in your SELECT list.
When you write SELECT date_trunc('minute', event_timestamp) AS event_timestamp, the PARTITION BY clause tries to bind to your alias rather than the base column. Since the alias is already the result of a function, StarRocks sees a nested expression (truncating a truncation), which the partition binder does not support.
The Fix: Use a unique name for your time column in the SELECT list.
sql
CREATE MATERIALIZED VIEW mv_minute
REFRESH ASYNC
-- This refers to the column in the base table
PARTITION BY date_trunc('hour', event_timestamp)
AS
SELECT
-- Use a different name for the alias
date_trunc('minute', event_timestamp) AS minute_ts,
count(*) as cnt
FROM my_table
GROUP BY 1;
2. Debugging the Rewrite (Version 4.0 Syntax)
In StarRocks 4.0, the explain rewrite command does not exist. Instead, use the TRACE command to get a definitive answer on why the rewrite failed:
sql
-- This will tell you exactly why mv_hour was rejected for the 'day' query
TRACE REASON MV SELECT date_trunc('day', event_timestamp), sum(cnt) FROM my_table GROUP BY 1;
Possible reasons you might see in the trace:
* Consistency: If mv_hour is set to "checked" but hasn't finished its hourly refresh, it will be rejected.
* Expression Mismatch: The optimizer may struggle to link date_trunc('day', event_timestamp) (Query) to date_trunc('hour', minute_ts) (MV) if the naming or functions are inconsistent.
3. Solution for "Day" Query Rewrite
To get a hierarchical rewrite to work for a coarser grain (like day) when the MVs are at finer grains (minute and hour), follow these requirements:
A. Use date_trunc consistently
Ensure all MVs in the chain use date_trunc rather than time_slice. While time_slice is more flexible, the StarRocks AggregateTimeSeriesRule is specifically optimized to recognize the hierarchy of date_trunc units (minute $\rightarrow$ hour $\rightarrow$ day $\rightarrow$ month).
B. Align the Consistency
Since your mv_hour is hierarchical, its freshness depends on mv_minute. If mv_minute has new data but mv_hour hasn't run its hourly refresh yet, a "checked" consistency setting will cause the query to fall back to the base table to ensure accuracy.
* Recommendation: Set both to loose consistency during testing to verify the rewrite logic works.
sql
ALTER MATERIALIZED VIEW mv_hour SET ("query_rewrite_consistency" = "loose");
C. The Correct Hierarchical Structure
sql
-- MV 1: Minute Rollup
CREATE MATERIALIZED VIEW mv_minute
PARTITION BY date_trunc('hour', event_timestamp)
AS
SELECT
date_trunc('minute', event_timestamp) AS minute_ts,
sum(val) as sum_val
FROM base_table
GROUP BY 1;
-- MV 2: Hour Rollup (Hierarchical)
CREATE MATERIALIZED VIEW mv_hour
PARTITION BY date_trunc('day', minute_ts)
AS
SELECT
date_trunc('hour', minute_ts) AS hour_ts,
sum(sum_val) as sum_val
FROM mv_minute
GROUP BY 1;
With this setup, StarRocks 4.0 should be able to transparently rewrite a query for date_trunc('day', event_timestamp) by rolling up mv_hour. If it still fails, the TRACE REASON MV output will pinpoint if it's a specific aggregate function or a partition alignment issue.
Summary of Commands for 4.0.8:
* Check Rewrite Status: TRACE REASON MV <your_query>
* Check Detailed Logs: TRACE LOGS MV <your_query>Rocky
04/06/2026, 11:21 PMSELECT * FROM information_schema.mtasks; (Check LAST_REFRESH)
References
• sql-reference/sql-functions/date-time-functions/date_trunc.md
• best_practices/partitioning.md
• sql-reference/sql-functions/date-time-functions/time_slice.md
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.mdCliff Resnick
04/06/2026, 11:56 PMevent_timestamp is not found in query statement. Ultimately it seems I have two choices: 1. partition by minute (not going to happen!) 2. Add both time_slice and date_trunc minute fields in the minute_mv and partition by a date_trunc(time_slice). It will then be used in hour and day rollups, but at minute granularity I may as well do a second hour-partitioned MV off the base table at hourly refresh. If I do that with the hour MV at "checked" consistency with the minute MV as "loose", will the query rewrite union the hour and minute MVs for tailing data?
-- MV 1: Minute Rollup
CREATE MATERIALIZED VIEW mv_minute
PARTITION BY date_trunc('hour', event_timestamp)
AS
SELECT
date_trunc('minute', event_timestamp) AS minute_ts,
sum(val) as sum_val
FROM base_table
GROUP BY 1;Rocky
04/06/2026, 11:57 PM"Materialized view partition function date_trunc must related with column" occurs because the StarRocks partition binder needs a direct column reference or a specifically allowed function like time_slice to track partition dependencies.
* Fails: PARTITION BY date_trunc('hour', minute_timestamp) where minute_timestamp is defined as date_trunc('minute', ...). StarRocks sees this as a nested expression (trunc on a trunc) and can't guarantee the partition alignment.
* Works: If the alias in the SELECT list is a time_slice or a bare column.
2. The Solution: Use "Choice 2"
Your "Choice 2" is actually the best practice for this scenario. You can include both columns in your mv_minute. Since time_slice (1 minute) and date_trunc (minute) produce the exact same values, there is zero cardinality penalty.
To ensure date_trunc('day', event_timestamp) rewrites correctly, keep the alias event_timestamp for the minute-truncated column.
sql
CREATE MATERIALIZED VIEW mv_minute
REFRESH ASYNC
-- This works because 'ts_partition' is a simple alias of time_slice
PARTITION BY date_trunc('hour', ts_partition)
AS
SELECT
-- 1. Use time_slice for the partitioning alias
time_slice(event_timestamp, INTERVAL 1 MINUTE) AS ts_partition,
-- 2. Use the original name for the truncated column to help query rewrite
date_trunc('minute', event_timestamp) AS event_timestamp,
count(*) as cnt
FROM my_table
GROUP BY 1, 2;
3. Will query rewrite Union the Hour and Minute MVs?
The short answer is No. StarRocks query rewrite logic works by picking the best single MV that can satisfy the query.
* If you query the base table for date_trunc('day', event_timestamp), the optimizer will look at mv_hour.
* If mv_hour is stale (e.g., missing the last 2 hours because it's "checked" consistency), StarRocks will perform a Partition Union Rewrite.
* Crucially: It will union mv_hour with the base table (my_table), not with mv_minute.
The optimizer does not currently "chain" multiple MVs together in a single union to fill in gaps. It always falls back to the source of truth (the base table) for any data not yet present in the chosen MV.
4. Correct Debugging Commands for 4.0.8
You are right that explain rewrite is not a StarRocks command. In version 4.0.8, the standard way to debug MV rewrites is using the TRACE command:
* To see the concise reason for failure:
sql
TRACE REASON MV SELECT date_trunc('day', event_timestamp), sum(cnt) FROM my_table GROUP BY 1;
* To see the detailed step-by-step logs of the optimizer:
sql
TRACE LOGS MV SELECT date_trunc('day', event_timestamp), sum(cnt) FROM my_table GROUP BY 1;
_(Note: If your client doesn't display the output, ensure you are using a standard MySQL-compatible client or check the FE fe.log if trace_log_mode is set to 'file'.)_
Summary Recommendation
1. Hierarchical approach: Build mv_hour on top of mv_minute. This simplifies management and ensures they stay "in sync."
2. Consistency: If you need the latest data, StarRocks will automatically pull the "tail" from the base table. Since your mv_minute is at a 1-minute grain, the amount of data pulled from the base table to fill the gap for mv_hour will be very small, making the fallback quite fast.
3. Variable: The variable enable_query_rewrite_for_complex_expression was deprecated/merged. In 4.0.8, you mainly just need SET enable_materialized_view_rewrite = true; (which is default).
Does this help clarify the path forward?
ReferencesRocky
04/06/2026, 11:57 PMCliff Resnick
04/07/2026, 12:13 AMRocky
04/07/2026, 12:14 AM"Materialized view partition column in partition exp must be base table partition column" happens because of a strict rule in StarRocks:
* Level 1 MV: Must be partitioned by an expression of the base table's partition column.
* Level 2 MV (Nested): Must be partitioned by an expression of the Level 1 MV's partition column.
If mv_minute is partitioned by ts_partition, then mv_hour cannot be partitioned by event_timestamp. It must also use ts_partition.
2. The Solution: Chained Partitioning and Consistent Rollup
To fix both the partitioning error and the rewrite failure, use this "Double Column" pattern. This preserves the partition chain while giving the optimizer a "clean" column to use for the date_trunc('day', ...) rollup.
Level 1: mv_minute
We use ts_partition to satisfy the binder and event_timestamp to satisfy the rewrite optimizer.
sql
CREATE MATERIALIZED VIEW mv_minute
REFRESH ASYNC
PARTITION BY date_trunc('hour', ts_partition)
AS
SELECT
time_slice(event_timestamp, INTERVAL 1 MINUTE) AS ts_partition, -- For partitioning
date_trunc('minute', event_timestamp) AS event_timestamp, -- For query rewrite
count(*) as cnt
FROM my_table
GROUP BY 1, 2;
Level 2: mv_hour
We must partition by ts_partition (the partition key of mv_minute) to avoid the binder error.
sql
CREATE MATERIALIZED VIEW mv_hour
REFRESH ASYNC
PARTITION BY date_trunc('day', ts_partition)
AS
SELECT
time_slice(ts_partition, INTERVAL 1 HOUR) AS ts_partition, -- Chain the partition key
date_trunc('hour', event_timestamp) AS event_timestamp, -- Chain the rewrite key
sum(cnt) as cnt
FROM mv_minute
GROUP BY 1, 2;
3. Regarding your "Union" question
Will the query rewrite union the hour and minute MVs for tailing data?
No. StarRocks query rewrite (specifically the "Partition Union Rewrite") only knows how to union one chosen MV with the base table.
* If you query for a day, and mv_hour is chosen but is 2 hours stale, StarRocks will union mv_hour with the base table (my_table) for those 2 hours.
* It will not attempt to fill the 2-hour gap using mv_minute.
Why this is okay: Since your mv_minute is at a 1-minute grain, the data in the "gap" of the base table is usually very small. Scanning 2 hours of the base table to supplement the mv_hour is typically very fast in StarRocks.
4. Debugging the "Rewrite group by key failed" error
The error Rewrite group by key failed: date_trunc(hour, 89: event_timestamp) usually means the optimizer sees the column but can't "prove" the query's date_trunc is a safe rollup of the MV's column.
In 4.0.8, use these commands to see exactly why it's failing:
1. Concise Reason: TRACE REASON MV <your_query>;
2. Step-by-Step Logs: TRACE LOGS MV <your_query>;
_ _Look for AggregateTimeSeriesRule in the logs. It should show if it's attempting to match the time granularities.*
Final Recommendation
If the hierarchical setup remains too complex or the rewrite continues to fail for the day granularity, the "Parallel" approach is actually more robust in StarRocks:
1. mv_minute: Partitioned by hour off the base table.
2. mv_hour: Partitioned by day off the base table (not nested).
3. mv_day: (Optional) Partitioned by month off the base table.Rocky
04/07/2026, 12:14 AMday queries always hit a materialized view.
References
* Troubleshooting Async MVs
* TRACE Statement for Rewrite
* Nested MV Rewrite Logic
References
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.md
• knowledge/usage/best-practice.md
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.mdCliff Resnick
04/07/2026, 1:27 AMCliff Resnick
04/07/2026, 2:46 PMexplain for the following query from a base view rand_delivery_parsed.
explain SELECT 'campaign_id', date_trunc('day',event_timestamp), HLL_CARDINALITY(HLL_UNION(HLL_HASH(uid))), sum(bid_cpm)/1000000, count(1) FROM rand_delivery_parsed where event_timestamp >= '2026-03-30' and event = 'impression' group by 1,2 order by 1,2 limit 100Rocky
04/07/2026, 2:46 PMexplain plan confirms a very powerful feature introduced in StarRocks 3.3 and enhanced in 4.0.8: Multi-MV Union Rewrite.
1. Confirming Multi-MV Union Rewrite
In StarRocks 3.3.0 and later, the optimizer was upgraded to support "Union rewrite based on multiple materialized views."
Previously, the "Partition Union Rewrite" only knew how to union one Materialized View with the base table to fill in gaps (stale/new partitions). In 4.0.8, the optimizer is recursive: it can satisfy a query by stitching together multiple MVs that cover different parts of the data range.
In your scenario:
* mv_day provides the bulk of historical data.
* mv_hour provides the data that has been refreshed at the hourly grain but isn't yet in the daily rollup.
* mv_minute (if present) provides the very latest data.
* The Base Table provides the "tail" of data that hasn't been captured by any MV yet.
The optimizer combines these using UNION ALL and then performs a final aggregation to merge the partial results (e.g., merging HLL states or summing counts). This explains why your explain plan shows multiple MVs being hit simultaneously.
2. Variables & Commands in 4.0.8
You observed that some variables and commands from older blogs or versions aren't present. Here is the status for 4.0.8:
* enable_query_rewrite_for_complex_expression: This variable was largely a "feature flag" during development. In 4.0.8, this capability is typically enabled by default as part of the core SPJG rewrite engine. The modern equivalent control is:
sql
SET enable_materialized_view_rewrite = true; -- Default
SET enable_materialized_view_union_rewrite = true; -- Controls the Multi-MV Union logic
* explain rewrite: This command does not exist. The correct way to see the rewrite logic in 4.0.8 is:
1. EXPLAIN <query>;: Look for "MaterializedView: true" and check if the Scan nodes refer to your MV names.
2. TRACE REASON MV <query>;: This is the dedicated command to see why an MV was or wasn't chosen.
3. TRACE LOGS MV <query>;: Provides the deep internal optimizer steps for the rewrite.
3. Why date_trunc('day') is working now
Your discovery that the explain plan shows both MVs explains why your earlier attempts with date_trunc('day') on a single MV were failing. The optimizer was likely looking for a way to satisfy the entire query range.
* If you queried for 7 days, and your mv_hour only had 2 days of data, a non-union rewrite would fail and fall back to the base table.
* With Union Rewrite active, StarRocks realizes it can take 6.5 days from mv_day and 12 hours from mv_hour to fulfill your date_trunc('day') query.
4. Hierarchical Freshness
The fact that you are hitting hierarchical MVs in one query means you have achieved a "Best of Both Worlds" setup:
* Historical queries are extremely fast (hitting the daily rollup).
* Recent queries are still fast (hitting the hourly rollup).
* Data is always consistent, as the optimizer automatically supplements missing parts from the finest grain available.
One tip for your HLL usage:
Since you are using HLL_UNION(HLL_HASH(uid)), make sure your MVs store the HLL state (using HLL_UNION) rather than just the final cardinality. This allows the Multi-MV Union to mathematically merge the HLL buckets from the different MVs correctly!
Summary for 4.0.8:
* Chaining MVs: Supported and automatically used via enable_materialized_view_union_rewrite.
* Debugging: Use TRACE REASON MV and TRACE LOGS MV.
* Variable Names: Many "experimental" flags from the 2.x/3.x era have been merged into the default behavior or simplified in 4.0.
References
• release_notes/release-4.0.mdCliff Resnick
04/08/2026, 2:05 PMRocky
04/08/2026, 2:05 PMBase -> Minute MV -> Hour MV) setup.
1. How CBO chooses Union over Single MV
The optimizer doesn't "prefer" a union; it calculates the total cost. In a hierarchical setup, the cost of scanning a row in the HOUR MV is significantly lower than in the MINUTE MV (roughly 1/60th the cost if the data is evenly distributed).
* Recursive Coverage: When you query the base table, the optimizer first checks if the HOUR MV can satisfy the query. If your query range is 08:15 to 11:45, the optimizer sees that the HOUR MV only has full partitions for 09:00 and 10:00.
* The "Gap" Detection: Instead of giving up on the HOUR MV, the optimizer identifies the "gaps" (08:15-09:00 and 11:00-11:45). It then recursively looks for the best way to fill those gaps.
* The Winner: It compares:
1. Scanning the MINUTE MV for the entire range (08:15-11:45).
2. Scanning the HOUR MV for the middle 2 hours + scanning the MINUTE MV for the two small edge fragments.
* Decision: Because the row count for the middle 2 hours in the HOUR MV is so much smaller than in the MINUTE MV, the "Union" plan will have a much lower total cost and will be chosen.
2. The Role of Consistency (checked vs loose)
Consistency tells the optimizer whether it can trust the data currently in the MV partitions.
Case A: Both are loose
* What happens: The optimizer assumes both MVs are perfectly synchronized with their sources, even if they aren't.
* The Decision: For the "leading/trailing" data, the CBO will still prioritize the MINUTE MV because the HOUR MV simply doesn't have those partitions yet.
* The Risk: If you have loose consistency on the HOUR MV, and the MINUTE_MV receives an update for an existing hour (e.g., late-arriving data for 09:00), the query will hit the HOUR MV for that hour and will not see the new data until the HOUR MV is explicitly refreshed.
Case B: HOUR MV is checked, MINUTE MV is loose (Recommended)
* What happens: The optimizer checks if the HOUR MV is stale compared to the MINUTE MV.
* The Decision: If the MINUTE MV has been updated but the HOUR MV hasn't refreshed yet, the optimizer will realize the HOUR MV partitions are "stale."
* The Result: It will "fall back" and pull those specific hours from the MINUTE MV instead. This ensures you always see the most recent data available in the MINUTE MV chain, even if the HOUR MV refresh is lagging.
3. Recommended Consistency Strategy
For your desired outcome, I recommend:
1. MINUTE_MV: loose consistency. This prevents the minute-level MV from constantly checking the base table's metadata (which can be heavy) and ensures it is always available for query rewrite.
2. HOUR_MV: checked consistency. This ensures that if the MINUTE_MV is refreshed, the HOUR_MV "knows" it's behind.
3. The Result:
* For old, stable data: Query hits HOUR_MV.
* For the "current" hour: Query hits MINUTE_MV (because the hour partition doesn't exist in Hour MV yet).
* For the "just finished" hour: If the HOUR_MV hasn't refreshed yet, the query hits MINUTE_MV (ensuring accuracy). Once the HOUR_MV refreshes, the query automatically switches to the more efficient HOUR_MV.
4. How to Verify
In 4.0.8, you can see the CBO's logic by running:
sql
TRACE LOGS MV <your_query>;
Look for sections mentioning UnionRewrite. You will see the optimizer calculating the "Pruned Partition" ranges for each MV. It will show something like:Rocky
04/08/2026, 2:05 PMMV: hour_mv, partitions: [2026033009, 2026033010]
* MV: minute_mv, partitions: [202603300815...202603300859, 202603301100...202603301145]
Note on Row Counts: For the CBO to make the right choice, ensure you have run ANALYZE TABLE <mv_name> so it knows the HOUR MV is actually smaller/cheaper than the MINUTE MV. Without stats, it may default to the "closest" MV in the hierarchy.
References
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.mdCliff Resnick
04/08/2026, 2:48 PMRocky
04/08/2026, 2:49 PMHOUR MV is creating years of partitions and how to fix it in version 4.0.8.
1. The "Metadata Ghost" (Most Likely)
Even if your MINUTE MV appears to only have recent data, it is likely "mirroring" the partition metadata of the base table.
* The Chain: Base Table (Hourly) -> MINUTE MV (Hourly) -> HOUR MV (Daily).
* If your Base Table has historical partitions (even if they are empty or were created via dynamic_partition.start = -500), the MINUTE MV inherits those partition definitions to maintain alignment.
* When you create the HOUR MV, StarRocks looks at the partition list of the MINUTE MV. If there are hourly partitions defined for the last 2 years (approx. 17,500 partitions), the HOUR MV will attempt to roll them up into ~730 daily partitions.
How to verify:
Run SHOW PARTITIONS FROM mv_minute_name;. Look at the PartitionName and Range columns. You will likely see thousands of hourly partitions going back years, even if Rows is 0.
2. Check for dynamic_partition Properties
Did you include properties like "dynamic_partition.start" or "dynamic_partition.end" in the PROPERTIES block of your HOUR MV?
* If these properties are present, the MV may ignore the base table's actual data range and instead follow the fixed rules of the Dynamic Partitioning scheduler.
* If the base table has dynamic_partition.start = -500, and you didn't specify a TTL for the MVs, they will faithfully recreate that entire 500-day window.
3. The Solution: Apply a Strict TTL
In StarRocks, the best way to prevent this "partition explosion" in hierarchical MVs is to set a partition_ttl_number (or partition_ttl) on every level of the hierarchy.
When you specify a TTL, StarRocks will only create and maintain the most recent N partitions.
* For mv_minute: Keep the last 72 hours (3 days).
* For mv_hour: Keep the last 30 days.
sql
-- Apply this to your existing MV or recreate it
ALTER MATERIALIZED VIEW mv_hour SET ("partition_ttl_number" = "30");
Note: Once you set this, StarRocks will automatically drop the "extra" historical partitions during the next refresh cycle.
4. The date_trunc vs time_slice Binding
You mentioned: _"The only way it compiles is to date_trunc the 'time_slice' minute MV column."_
This is the correct and only way in 4.0.8. Because of the strict "must be base table partition column" rule:
1. The MINUTE MV must be partitioned by a column (let's call it m_partition).
2. The HOUR MV must be partitioned by an expression of m_partition.
3. If m_partition is the result of time_slice(..., INTERVAL 1 MINUTE), then date_trunc('day', m_partition) is the valid way to chain the logic.
Troubleshooting Checklist
1. Run SHOW PARTITIONS FROM [base_table]: If the base table has partitions for the last 3 years, your MVs will too, unless you use partition_ttl_number.
2. Run SHOW CREATE MATERIALIZED VIEW mv_hour: Check if there are any dynamic_partition properties that were accidentally included.
3. Check for "Empty" Partitions: Even if a partition has 0 rows, it is a metadata object. HOUR MV will create a daily partition if there is at least one hourly partition in MINUTE MV that falls within that day.
4. Use partition_ttl:
sql
PROPERTIES (
"partition_ttl_number" = "30", -- Keep 30 days
"partition_refresh_number" = "1" -- Refresh 1 day at a time to save memory
)Rocky
04/08/2026, 2:49 PMHOUR MV has more partitions because it is aligning with the full history of the base table's partition metadata, not just the rows you currently see. Setting "partition_ttl_number" is the standard way to prune this history.
References
• using_starrocks/async_mv/feature-support-asynchronous-materialized-views.md
• using_starrocks/async_mv/use_cases/data_modeling_with_materialized_views.md
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.mdCliff Resnick
04/08/2026, 3:01 PMRocky
04/08/2026, 3:01 PMmvPartitionsToRefresh: [] and an "insert overwrite" of the entire table typically indicates that Partition Alignment has failed. When alignment fails, the Materialized View (MV) cannot perform incremental (partition-by-partition) refreshes and falls back to a Complete Refresh.
Here is why this is likely happening in your hierarchical setup and how to fix it:
1. The "Empty List" ([]) Meaning
In task_runs, mvPartitionsToRefresh is populated only if StarRocks successfully maps the base table's partitions to the MV's partitions.
* If the list is []: StarRocks has decided that it cannot determine which specific partitions need refreshing. This usually triggers a COMPLETE REFRESH where it attempts to overwrite the entire MV in one giant transaction, ignoring partition_refresh_number = 1.
2. Common Causes for Alignment Failure in Nested MVs
For your HOUR MV to be "partition-aligned" with your MINUTE MV, the following must be true:
* Rule 1: The Source Column must be the Partition Key.
If your MINUTE MV is partitioned by a column (e.g., min_granule), the HOUR MV must be partitioned by an expression directly using that column.
_ _Incorrect:* PARTITION BY date_trunc('day', original_event_timestamp)
_ _Correct:* PARTITION BY date_trunc('day', minute_mv_partition_column)
* Rule 2: Function Compatibility.
Only certain functions are supported for partition alignment (e.g., date_trunc, time_slice). In 4.0.8, if you use a complex expression or a non-standard function, the CBO loses the ability to "link" the partitions.
* Rule 3: Consistency of Types.
If the partition column in the MINUTE MV is a DATETIME and you are trying to partition the HOUR MV by a STRING or a casted type, the alignment may break.
3. Why it creates "Hundreds of Partitions"
You mentioned the HOUR MV has partitions going back years. When alignment fails:
1. StarRocks treats the MV as a non-incremental entity.
2. However, because you have a PARTITION BY clause, it looks at the entire history of the base table (or base MV) to determine what partitions could exist.
3. If your MINUTE MV inherited metadata from a base table that has 3 years of partitions, the HOUR MV will try to create all ~1,000 daily partitions at once during that initial "Complete Refresh."
4. How to Fix It
To force the HOUR MV to refresh partition-by-partition and stop the full-table overwrite:
A. Verify the Partition Key Link
Run SHOW CREATE MATERIALIZED VIEW mv_hour_name. Ensure the PARTITION BY clause looks like this:
sql
PARTITION BY date_trunc('day', name_of_partition_column_in_minute_mv)
Note: It must use the column name as it exists in the MINUTE MV's schema.
B. Apply a Partition TTL Immediately
If the MV is currently trying to create years of data, stop the task and add a TTL:
sql
ALTER MATERIALIZED VIEW hour_mv_name SET ("partition_ttl_number" = "30");
This forces the MV to only care about the last 30 days of partitions, which usually "unblocks" the alignment logic because it doesn't have to deal with thousands of historical metadata entries.
C. Manual Partition Refresh (The "Jumpstart")
Sometimes the initial refresh of a nested MV defaults to "Complete" because the system hasn't established the version mapping yet. You can "force" it into partition mode by manually refreshing a small range:
sql
REFRESH MATERIALIZED VIEW hour_mv_name
PARTITION START ("2026-03-01") END ("2026-03-05")
FORCE WITH SYNC MODE;
After a successful manual partition refresh, the ASYNC scheduler usually "learns" the alignment and will populate mvPartitionsToRefresh in subsequent runs.
Summary Checklist for 4.0.8
1. Is the HOUR MV partitioned by a date_trunc of the `MINUTE MV`'s partition key?
2. Does the MINUTE MV have many "empty" historical partitions? (If so, use partition_ttl_number on the MINUTE MV too).Rocky
04/08/2026, 3:01 PMTRACE REASON MV <query> on a query that should hit the HOUR MV. It will often tell you if the MV is considered "inactive" or "not aligned."
References
• using_starrocks/async_mv/use_cases/data_lake_query_acceleration_with_materialized_views.md
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.md
• using_starrocks/async_mv/Materialized_view.md
• using_starrocks/async_mv/feature-support-asynchronous-materialized-views.mdCliff Resnick
04/08/2026, 3:27 PMRocky
04/08/2026, 3:27 PMStarRocks planner use long time... in logical phase error on a simple COUNT query over a small number of partitions is a strong signal that the Frontend (FE) is overwhelmed by metadata, rather than the query itself being complex.
Since you are dealing with a MINUTE MV partitioned by hour, even 100 partitions should plan in milliseconds. If it takes 38 seconds, the FE is likely stuck traversing an massive number of "phantom" metadata objects or experiencing extreme Java Garbage Collection (GC) pressure.
1. How to fix without recreating the view
You can often "unstick" a Materialized View without a full drop/create by addressing the metadata and statistics directly:
* Update Statistics (The most likely fix):
The planner uses statistics to build the logical plan. If the statistics are stale or corrupted, the CBO can enter an infinite loop or a very long search path. Force an update:
sql
ANALYZE TABLE your_minute_mv_name;
* Increase the Planner Timeout:
To at least allow the query to finish so you can inspect it, increase the session or global timeout:
sql
SET global new_planner_optimize_timeout = 60000; -- Increase to 60s
* Check for Tablet Inflation:
Run SHOW TABLE STATUS LIKE 'your_minute_mv_name'; and check the Buckets or run SHOW PARTITIONS FROM your_minute_mv_name;.
* The Trap: If you didn't specify a DISTRIBUTED BY clause or used a very high bucket count, and you have many partitions, you might have created tens of thousands of tablets. The planner must check the health of every tablet during the logical phase.
* Fix: If the tablet count is in the tens of thousands, you will eventually need to recreate it with a better BUCKETS strategy (e.g., DISTRIBUTED BY HASH(join_key) BUCKETS 16).
* Force Manual Refresh of Recent Data:
Sometimes the metadata mapping between the base table and MV gets "tangled." Manually refreshing the last few partitions can sometimes reset the version pointers:
sql
REFRESH MATERIALIZED VIEW your_minute_mv_name PARTITION START ("2024-01-01 00:00:00") END ("2024-01-01 23:59:59") FORCE;
──────────
2. Can frequent REFRESH intervals cause this?
Yes. Frequent refreshes (e.g., every 1 minute) are the leading cause of "Metadata Bloat" in StarRocks MVs.
* Version Explosion: Each refresh creates a new "version" of the metadata. If the cleaning process (compaction of metadata) cannot keep up with a 1-minute refresh cycle, the FE's memory becomes cluttered with thousands of old version records.
* Task Backlog: If a refresh takes 50 seconds and your interval is 60 seconds, the FE is almost constantly running planning logic for the next refresh while trying to serve your queries. This leads to CPU contention in the "Logical Phase."
* The "Compaction" Lag: Frequent writes to a partitioned table (even an MV) create many small "label" entries in the FE.
How to prevent it:
1. Set a Minimum Refresh Interval:
In your FE configuration, ensure materialized_view_min_refresh_interval is set (default is 60s). Avoid setting intervals shorter than 1 minute unless your data volume is tiny.
2. Use `partition_ttl`:
If your MINUTE MV is a "sliding window" for the HOUR MV, do not keep years of data in it.
sql
ALTER MATERIALIZED VIEW your_minute_mv_name SET ("partition_ttl_number" = "72"); -- Keep only 72 hours
This automatically drops old partitions and their associated metadata, keeping the FE "lean."
3. Monitor FE Health:
Check your FE logs (fe.gc.log). If you see frequent "Full GC," the FE doesn't have enough heap memory to hold the MV metadata. You may need to increase -Xmx in fe.conf.
4. Batch your Base Table Loads:
If the MINUTE MV is triggered by data arriving in the base table, try to batch those base table loads (e.g., every 5 minutes) rather than trickling data in every few seconds.Rocky
04/08/2026, 3:27 PMANALYZE TABLE doesn't fix it, run EXPLAIN VERBOSE <your_query> and look at the "Cost" section. If the cost numbers look like scientific notation (e.g., 1.4e12), the statistics are definitely the culprit.
References
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.md
• administration/management/FE_configuration.md
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.md
• using_starrocks/async_mv/use_cases/data_modeling_with_materialized_views.md
• using_starrocks/async_mv/materialized_view_task_run_details.mdCliff Resnick
04/08/2026, 4:42 PMRocky
04/08/2026, 4:42 PMANALYZE TABLE and EXPLAIN are failing, your FE's metadata for this MV is likely in a state where the cost-based optimizer (CBO) cannot resolve the plan. You will need to rebuild.
Here is the strategy to rebuild while handling your specific data retention requirements (3 days for Minute, 30 days for Hour).
1. Do you need to rebuild the HOUR MV?
Yes. In StarRocks, MVs depend on the unique internal ID of their base tables/MVs.
* If you drop and recreate the MINUTE MV, it gets a new ID.
* The HOUR MV will immediately become INACTIVE.
* While you can try ALTER MATERIALIZED VIEW hour_mv ACTIVE, this command essentially tries to "re-link" the logic. However, because the source data (the new MINUTE MV) will initially be empty or missing 30 days of history, a refresh will likely fail or wipe out your historical HOUR data.
* Recommendation: Drop both and rebuild them in sequence to ensure the metadata is clean and the IDs are correctly mapped.
2. How to get 30 days of HOUR data with only 3 days of MINUTE data?
In a hierarchical setup (Base -> Minute MV -> Hour MV), the Hour MV can only refresh a partition if the source data exists in the Minute MV. If you set the Minute MV to a 3-day TTL, you can never (re)fresh the Hour MV for any data older than 3 days.
Option A: The "Direct Link" Strategy (Recommended)
Instead of nesting them, point the HOUR MV directly to the Base Table.
* Why? This decouples their lifespans. You can keep 3 days of hourly aggregates and 30 days of daily aggregates without one limiting the other.
* Performance: If your base table is partitioned by hour, StarRocks is very efficient at rolling up hourly partitions into a daily MV partition. You likely won't see a significant performance hit compared to the nested approach.
Option B: The "Temporary Backfill" (Hierarchical)
If you must keep the hierarchy for compute savings:
1. Recreate MINUTE MV with a temporary TTL of 30 days: "partition_ttl_number" = "720".
2. Refresh MINUTE MV for the full 30 days (use partition_refresh_number = 10 to speed it up).
3. Create/Rebuild HOUR MV with a 30-day TTL.
4. Refresh HOUR MV for the full 30 days.
5. Once healthy, ALTER the MINUTE MV to change the TTL back to 3 days:
sql
ALTER MATERIALIZED VIEW mv_minute SET ("partition_ttl_number" = "72");
Note: The HOUR MV will keep its 30 days of data. However, if you ever need to "FORCE REFRESH" a day from 2 weeks ago, it will fail because the MINUTE MV source partitions are gone.
3. How to prevent the "Planner Timeout" in the future
The "years of partitions" was caused by the MV inheriting the partition list from your base table's history. To prevent this:
1. Always use partition_ttl_number on creation: Never create a partitioned MV in a production-sized environment without a TTL. This keeps the metadata small.
2. Avoid 1-minute Refreshes: If your MINUTE MV is refreshing every 60 seconds, it creates a massive amount of "version metadata."
* If you need near-real-time, check if you can increase the interval to 5 or 10 minutes.
* Check the FE logs for "Full GC" messages; if the FE is out of memory, the planner will time out.
3. Set Bucket Limits: Ensure your MVs have a reasonable bucket count. If you have 1,000 partitions and each has 32 buckets, the FE has to manage 32,000 tablets. Use a smaller number like BUCKETS 4 or 8 for aggregate MVs.
Summary of Rebuild Order (Option A - Safest)
1. DROP MATERIALIZED VIEW mv_hour;Rocky
04/08/2026, 4:42 PMDROP MATERIALIZED VIEW mv_minute;
3. CREATE MATERIALIZED VIEW mv_minute ... PROPERTIES("partition_ttl_number" = "72") ... (Source: Base Table)
4. CREATE MATERIALIZED VIEW mv_hour ... PROPERTIES("partition_ttl_number" = "30") ... (Source: Base Table, not Minute MV)
This setup is much more robust against metadata "corruption" and allows the two views to have independent retention policies.
References
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.md
• using_starrocks/async_mv/use_cases/data_modeling_with_materialized_views.md
• using_starrocks/async_mv/Materialized_view.md
• sql-reference/sql-statements/materialized_view/DROP_MATERIALIZED_VIEW.mdCliff Resnick
04/08/2026, 4:55 PMRocky
04/08/2026, 4:56 PMANALYZE TABLE, and your partition count is low, there are three non-obvious technical reasons for this behavior:
1. Materialized View Rewrite "Combinatorial Explosion"
Even for a simple COUNT(*) query, the StarRocks planner doesn't just look at your MV. By default, it tries to determine if other Materialized Views can be used to optimize the query.
* The Problem: If you have multiple MVs or complex nested MVs, the planner’s Query Rewrite engine can enter a high-complexity search phase. It evaluates different combinations of MVs to find the most efficient execution path.
* How to verify: Run the query with the rewrite engine disabled in your session:
sql
SET enable_materialized_view_rewrite = false;
SELECT count(*) FROM your_minute_mv;
If the query is suddenly instant, the "corruption" isn't in the data, but in the Rewrite Metadata. Rebuilding the MVs resets the search space for the optimizer.
2. High Tablet/Bucket Density
You mentioned having "less than 100 partitions," but the planner's logical phase also considers the total number of tablets.
* The Trap: If your MINUTE MV was created with a high number of buckets (e.g., DISTRIBUTED BY HASH(id) BUCKETS 32) and you have 100 partitions, that’s 3,200 tablets. For a minute-level MV, if the bucket count isn't specified, StarRocks may use a default that is too high for the volume.
* Planner Impact: The logical phase involves checking the health and location of every tablet. If the FE (Frontend) is experiencing network latency or high CPU, this "tablet lookup" phase can time out.
* Prevention: When you rebuild, explicitly set a low bucket count for your MVs if the data volume per partition is small:
sql
DISTRIBUTED BY HASH(id) BUCKETS 1 -- or a small number like 4
3. FE Heap Memory and "Version Bloat"
Even if there aren't many partitions, frequent refreshes (e.g., every 1-5 minutes) create a high volume of version metadata in the FE's memory.
* The Cause: Every time an MV refreshes, a new "Version" is recorded. If the FE's Java Garbage Collector (GC) cannot keep up with the churn of these metadata objects, the "Logical Phase" of the planner ( which is heavily memory-intensive) will slow down significantly.
* How to check: Check your FE logs (fe.gc.log or fe.log). Look for "Full GC" or "Metadata manager" warnings.
* Fix: Increase the FE heap memory (in fe.conf, JAVA_OPTS="-Xmx8g" or higher) and ensure your refresh interval isn't so frequent that it outpaces the FE’s metadata cleaning cycle.
Regarding your Rebuild Strategy:
You can maintain 3 days of MINUTE MV and 30 days of HOUR MV without a full rebuild of the HOUR MV every time, provided you use the "Direct Link" approach:
1. Directly source HOUR MV from the Base Table: Instead of SELECT ... FROM minute_mv, define the HOUR MV to select from your actual raw data table.
2. Why this works: StarRocks is extremely efficient at aggregating raw data into hourly chunks. By pointing both MVs to the base table, the HOUR MV can look back 30 days regardless of what the MINUTE MV is doing. This prevents the "ID mismatch" that happens if you drop and recreate the middle layer.
3. If you must use nesting: If you keep the HOUR MV pointed at the MINUTE MV, you must keep 30 days of data in the MINUTE MV as well, or the HOUR MV will lose its ability to refresh older partitions.
Summary Checklist for the Rebuild:
* [ ] Explicit BUCKETS: Use a small number (BUCKETS 1 or 4) for aggregate MVs.
* [ ] TTL: Always include PROPERTIES("partition_ttl_number" = "...").Rocky
04/08/2026, 4:56 PMASYNC, ensure the interval is at least 2-5 minutes to avoid metadata version explosions.
* [ ] Rewrite Check: If the problem persists on the new view, try SET global enable_materialized_view_rewrite = false;.
References
• using_starrocks/async_mv/materialized_view_task_run_details.md
• using_starrocks/async_mv/feature-support-asynchronous-materialized-views.md
• sql-reference/sql-statements/materialized_view/REFRESH_MATERIALIZED_VIEW.mdCliff Resnick
04/08/2026, 4:58 PMRocky
04/08/2026, 4:58 PMDISTRIBUTED BY RANDOM is a major clue. While StarRocks supports random distribution for ease of use, it is generally not recommended for Materialized Views, especially in a tiered (Minute -> Hour) architecture.
Here is why RANDOM distribution likely caused your "Planner Timeout" and why switching to HASH will prevent it:
1. The "Metadata Explosion" in the Planner
The Logical Phase of the planner is where the Cost-Based Optimizer (CBO) and the Query Rewrite engine live.
* No Bucket Pruning: With DISTRIBUTED BY RANDOM, the planner cannot prune buckets. It must consider every single tablet in every partition.
* Tablet Inflation: When you don't specify a bucket count with RANDOM, StarRocks uses "Auto-Bucketing." In high-frequency refresh scenarios (like every minute), if the data volume fluctuates, the "Auto-Bucketing" logic can sometimes create an inconsistent or excessive number of tablets across partitions.
* Logical Complexity: When you run a query, the planner doesn't just look at the MV; it evaluates if the query can be "rewritten" using other views. With RANDOM distribution, the optimizer has fewer "structural hints." If your metadata is already bloated from frequent refreshes, the search space for the rewrite logic becomes computationally expensive, leading to that 38-second hang.
2. Frequency + Random = High Version Churn
Every time a MINUTE MV refreshes:
1. It creates a new Version.
2. With RANDOM distribution, StarRocks has to manage the tablet mapping without the stability of a hash key.
3. If your refresh interval is very short (1 minute), the Frontend (FE) can accumulate thousands of metadata "version" objects. The planner has to traverse these versions to find the "Active" data.
4. The Result: Even a SELECT COUNT(*) requires the planner to resolve the current state of 100 partitions, each with multiple tablets and potentially hundreds of historical versions. This is likely why even EXPLAIN was failing—the metadata was too "heavy" for the Java Heap to process in time.
3. Recommendation for your Rebuild
When you rebuild, avoid RANDOM. Use HASH distribution and explicitly define your buckets. This makes the metadata "stable" and much lighter for the FE to handle.
Revised Create Statement Example:
sql
CREATE MATERIALIZED VIEW your_minute_mv
PARTITION BY hour_col
DISTRIBUTED BY HASH(some_id) BUCKETS 8 -- Explicitly set a small number of buckets
PROPERTIES (
"partition_ttl_number" = "72", -- Keep 3 days
"replication_num" = "3"
)
AS SELECT ...
4. Best Practice for Hierarchical MVs
Since you are rebuilding, here is the most stable way to handle the 3-day (Minute) vs. 30-day (Hour) requirement:
* Minute MV: Source from Base Table, 3-day TTL, HASH distributed.
* Hour MV: Source from Base Table (not the Minute MV), 30-day TTL, HASH distributed.
* Why? If you point the Hour MV at the Minute MV (nesting), the Hour MV's "Source of Truth" disappears after 3 days. If you ever need to fix a data issue from 10 days ago, you won't be able to refresh the Hour MV. Pointing both to the Base Table is much more resilient and avoids the "ID mismatch" problem if the Minute MV ever needs a rebuild again.
Summary: The combination of DISTRIBUTED BY RANDOM, frequent refreshes, and nested dependencies created a metadata "perfect storm." By switching to DISTRIBUTED BY HASH with an explicit bucket count and using the "Direct Link" (both MVs pointing to the base table), you will likely never see that planner timeout again.
Rocky's Pro-Tip: After you recreate the MVs, check SHOW TABLE STATUS LIKE 'mv_name'. Ensure the TabletCount is a reasonable number (e.g., < 5,000 total for the whole MV). If it's in the tens of thousands, you need fewer buckets!
ReferencesRocky
04/08/2026, 4:58 PMCliff Resnick
04/08/2026, 5:12 PM"mvPartitionsToRefresh":[],"refBasePartitionsToRefreshMap":{},"basePartitionsToRefreshMap":{}
Does that mean it is not aligned with base table partitions? Because it's actually being built from a view of the base tableRocky
04/08/2026, 5:13 PM"mvPartitionsToRefresh": [], "basePartitionsToRefreshMap": {}, it is a strong signal that partition alignment has failed.
Here is the breakdown of why this is happening and what it means for your rebuild:
1. The "Logical View" Problem
In StarRocks, incremental (partition-by-partition) refresh depends on a direct metadata link between the Materialized View's partition key and the Base Table's partition key.
* The Cause: When you build an MV off a Logical View (e.g., CREATE VIEW my_view AS SELECT ...), StarRocks treats the source as a non-partitioned entity. Logical Views themselves do not have partitions; they are just stored queries.
* The Result: Even if your CREATE MATERIALIZED VIEW statement includes a PARTITION BY clause, StarRocks cannot "see through" the logical view to map your MV partitions to the underlying base table partitions.
* The Behavior: Because it can't align them, StarRocks cannot do incremental refreshes. It defaults to a Full Refresh of the entire MV every time. In the task metadata, a full refresh of a potentially "unaligned" MV often shows an empty list [] because the system isn't targeting specific partitions—it’s just running the whole query as one block.
2. Is it "Aligned"?
No. If those maps are empty, your MV is not aligned with the base table.
* If the MV is not aligned, the partition_ttl_number and partition_refresh_number properties will not work as expected.
* The MV will not automatically refresh only the "new" data. It will likely try to re-process everything (or fail to identify what to update), which can lead back to the Planner Timeouts you saw earlier because the FE has to manage a "flat" unpartitioned dataset that is actually quite large.
3. How to fix it (The Rebuild Strategy)
To get the 3-day/30-day tiered logic working correctly and avoid the planner timeout, you should bypass the Logical View for the MV definition.
The Correct Approach:
Point the MINUTE MV directly to the OlapTable (the base table). If you need the logic inside your View, copy that SQL logic directly into the MV's AS SELECT statement.
sql
-- DON'T DO THIS:
CREATE MATERIALIZED VIEW minute_mv
PARTITION BY dt
AS SELECT ... FROM logical_view; -- Alignment fails here
-- DO THIS:
CREATE MATERIALIZED VIEW minute_mv
PARTITION BY base_table_dt -- Explicitly reference the table's partition column
DISTRIBUTED BY HASH(id) BUCKETS 8
PROPERTIES("partition_ttl_number" = "72")
AS
SELECT col1, col2, dt as base_table_dt
FROM base_olap_table -- Direct reference
WHERE ... ; -- Put your view logic here
4. Regarding your Hour MV
As we discussed earlier, if you want 30 days of HOUR data and only 3 days of MINUTE data:
1. Do not nest them if you want them to have different lifespans.
2. Build the HOUR MV directly from the base table as well.
3. Since they both point to the base table, StarRocks can independently track 3 days for one and 30 days for the other. This completely avoids the "ID mismatch" and "missing source data" problems.
Summary
The empty JSON maps confirm that your current MV is not tracking partitions. It is treating the source View as a single unpartitioned blob. For your rebuild to be successful and performant:
* Use the Base Table as the source, not a View.
* Use DISTRIBUTED BY HASH with a specific bucket count.
* Verify alignment after creation by running SHOW FRAGMENTS FROM mv_name or checking the next task run; you should see actual partition names in the mvPartitionsToRefresh list.
References
• using_starrocks/async_mv/materialized_view_task_run_details.mdCliff Resnick
04/08/2026, 5:57 PMRocky
04/08/2026, 5:58 PMmvPartitionsToRefresh, basePartitionsToRefreshMap) are empty despite building directly off the base table is a definitive sign that partition alignment has failed. When alignment fails, StarRocks cannot track which specific partitions are stale, so it defaults to a "Full Refresh" (or finds nothing to refresh), which explains the performance issues and the planner timeouts you've been seeing.
Here are the most likely reasons for alignment failure in your specific case:
1. Granularity Mismatch (The "Finer Granularity" Rule)
This is the most common reason for your specific setup (Minute/Hour MVs).
* The Rule: StarRocks supports "Rollup" alignment (Base Table $\rightarrow$ Coarser MV). For example, if your base table is partitioned by Day, you can have an MV partitioned by Month.
* The Problem: StarRocks does not support "finer" alignment (Base Table $\rightarrow$ Finer MV). If your base table is partitioned by Day, you cannot have an MV partitioned by Minute or Hour that is "aligned" for incremental refreshes.
* Why? If a single base partition (e.g., "2023-10-01") is updated, StarRocks doesn't know which of the 1,440 minute-level partitions in the MV need to be updated. It loses the mapping, alignment fails, and it treats the MV as a single unpartitioned blob for that refresh.
* Check: Run SHOW CREATE TABLE base_table. If the base table is partitioned by DAY and your MV is PARTITION BY ... MINUTE, alignment will never work.
2. Is the Base Table actually partitioned?
It sounds simple, but it’s a frequent pitfall.
* The Check: Run SHOW PARTITIONS FROM base_table.
* The Result: If you only see one row (usually named p0 or similar) or if the table was created without a PARTITION BY clause, it is a non-partitioned table.
* Constraint: You cannot create a partitioned (aligned) MV on a non-partitioned base table. StarRocks will allow you to create the MV with a PARTITION BY clause, but it will never "align" with the source.
3. Expression Mismatch in PARTITION BY
To align partitions, the expression in the MV's PARTITION BY must be a direct derivative that StarRocks recognizes.
* Failed Alignment: Using date_format(col, '%Y-%m-%d') or other string-manipulation functions often breaks the mapping.
* Successful Alignment: Use date_trunc('hour', col) or time_slice(col, INTERVAL 1 HOUR).
* Crucial Step: The column used in the PARTITION BY of the MV must be the same column (or derived from the same column) used for partitioning the base table.
4. Use of PROPERTIES ("partition_ttl_number" = "10")
If you set the TTL to 10 days, but the system doesn't see an alignment:
1. It searches the base table for partitions.
2. It fails to map them to the MV.
3. It concludes there are 0 partitions to refresh, resulting in your empty maps: [].
How to verify the specific failure:
Run this command to see why the optimizer is struggling with the MV:
sql
EXPLAIN REWRITE SELECT ... -- your MV query here
Or, more helpfully, check the planBuilderMessage in the task metadata. Often, if you look at the INFORMATION_SCHEMA.task_runs or use desc [task_id], there is a field that explains why the partition mapping was skipped.
Recommended Strategy for your Rebuild:
1. Match Granularity: If your base table is partitioned by DAY, your MINUTE MV will likely never align. You should instead partition the MINUTE MV by Day as well, but keep the data inside it at the minute level.
_ _Wait, how does that help?* If both are partitioned by Day, alignment works! If 1 minute of data arrives, StarRocks refreshes that entire Day partition (which is fast if you only keep 3 days of data).
2. Explicit Columns: Ensure your MV definition looks like this:
```sql
CREATE MATERIALIZED VIEW minute_mv
PARTITION BY base_partition_col -- Use the EXACT column from the base tableRocky
04/08/2026, 5:58 PMSTRING type partitions, ensure you are on StarRocks v3.1.4+ or v3.2+, as alignment for string partitions was not supported in earlier versions.
References
• using_starrocks/async_mv/materialized_view_task_run_details.md
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.mdCliff Resnick
04/08/2026, 6:00 PMRocky
04/08/2026, 6:01 PMMINUTE MV has a PROPERTIES("partition_ttl_number" = "72") (3 days), but you are trying to refresh data from 10 days ago, alignment will always fail for those older partitions.
* How it works: StarRocks only calculates alignment for partitions that fall within the MV's defined TTL.
* The Symptom: If you change the base table data from 5 days ago, the MV will see the change but say "This is outside my 3-day TTL window," and the mvPartitionsToRefresh will be empty.
* The "Sometimes" part: Alignment works for the recent 3 days of partitions but shows empty maps for anything older. If you want the MV to align for 10 days, you must set the partition_ttl_number to 240 (for hourly partitions) or higher in the MV properties.
2. Why it "Sometimes" works through a View
StarRocks generally does not support partition alignment through a Logical View.
* The Inconsistency: If the View is extremely simple (e.g., SELECT * FROM table), the optimizer occasionally "sees through" it during the initial creation or a full refresh.
* The Failure: As soon as you add a WHERE clause, a JOIN, or even an alias in that View, the metadata link is broken. The optimizer loses the "Partition Identity" of the underlying table, alignment fails, and it defaults to a Full Refresh (which then might show empty incremental maps).
* Rule of Thumb: Never use a View as a source for an MV if you need partition-level incremental refreshes.
3. Missing Column in SELECT
For partition alignment to work, the column used in the PARTITION BY clause of the MV must be present in the SELECT list of the MV.
* Failure Example:
CREATE MV ... PARTITION BY dt AS SELECT col1 FROM base_table; $\rightarrow$ Alignment Fails.
* Success Example:
CREATE MV ... PARTITION BY dt AS SELECT col1, dt FROM base_table; $\rightarrow$ Alignment Works.
4. Expression Mismatch (Hourly vs. Hourly)
Even if both are "Hourly," StarRocks needs a literal match or a supported date_trunc expression.
* If your base table is partitioned by a DATETIME column named event_time.
* Your MV should be: PARTITION BY date_trunc('hour', event_time).
* If you use date_format or cast it to a STRING in the MV's PARTITION BY, alignment will fail (unless you are on StarRocks v3.1.4+ and the base table is also STRING-partitioned).
How to Diagnose the "Empty Map"
To see exactly why StarRocks is failing to align the partitions, run this query to find the Reason Code:
sql
SELECT
task_id,
status,
extra_message -- Look for 'planBuilderMessage' inside this JSON
FROM information_schema.task_runs
WHERE mv_name = 'your_mv_name'
ORDER BY create_time DESC LIMIT 1;
Look for the planBuilderMessage field. It will often say something like:
* "The source table is not partitioned."
* "Partition column is not found in SELECT list."
* "TTL filtered out all partitions."
Final Recommendation for your Rebuild:
1. Bypass the View: Point the MV directly to the OlapTable.
2. Match TTL: If you want to see alignment for 10 days of data, set partition_ttl_number to 240 (since you are partitioned by Hour).
3. Include the Key: Ensure the partition column is explicitly in the SELECT list.
4. Use HASH: As we discussed, keep DISTRIBUTED BY HASH(some_col) BUCKETS 8 to keep the planner from timing out.Rocky
04/08/2026, 6:01 PMCliff Resnick
04/09/2026, 1:22 PMenable_cbo_view_based_mv_rewriteRocky
04/09/2026, 1:23 PMenable_cbo_view_based_mv_rewrite (and its sibling enable_view_based_mv_rewrite) is extremely high in your specific architecture—specifically because you are using a JSON base table and a shredded logical view.
Here is the breakdown of why this variable exists and what it does for your setup:
1. What it does (The "View Node" Strategy)
By default, StarRocks handles views by expanding them. If you query my_shredded_view, the optimizer immediately replaces the view name with the underlying JSON path logic (e.g., get_json_string(...)) and tries to match that raw logic to your Materialized View.
When you enable these variables, you tell the optimizer: "Don't expand the view yet. Keep it as a logical node and see if there is an MV that maps directly to this view."
2. Why it is "Highly Significant" for JSON Shredding
Shredding JSON via logical views is very convenient for users but has two major challenges:
* Performance: Executing JSON path lookups at query time is CPU-intensive and slow.
* Rewrite Complexity: The query optimizer is much more likely to "miss" a rewrite opportunity if it has to match a massive, complex JSON-shredding expression in your query to the exact same expression in your MV.
With the variable enabled: The optimizer can match the View Identity to the MV. This makes the query rewrite much more robust and reliable. It ensures that users querying your "clean" view are transparently routed to the "fast" MV that has already pre-calculated those JSON fields.
3. Why is it disabled by default?
StarRocks keeps it disabled by default for two main reasons:
1. Optimizer Overhead: It requires the Cost-Based Optimizer (CBO) to explore more "search space" during the planning phase. If you have hundreds of MVs and nested views, it can make the planning of a simple query take longer.
2. Plan Stability: In older versions, expanding the view early was a more "stable" way to ensure the most efficient physical plan was chosen. The "view-based" rewrite logic is newer and more advanced (stabilized in v3.3+).
4. Should you enable it?
Yes. Given that your base table is JSON and your view is shredded, you should set:
sql
SET GLOBAL enable_view_based_mv_rewrite = true;
SET GLOBAL enable_cbo_view_based_mv_rewrite = true; -- Available in newer v3.3+ releases
5. Interaction with Partitioning
There is one crucial distinction to keep in mind:
* Query Rewrite (The variable you found): This helps your users. It makes their queries against the VIEW fast by using the MV.
* Partition Alignment (Our previous discussion): This helps the system. It makes the MV refresh fast by only updating the last hour of data.
Even if the query rewrite works perfectly, the MV Refresh may still fail to align partitions if the MV is built on top of the view. This is why the best practice remains:
1. Build the MV directly on the Base Table (OlapTable) so the partitions align and the background refresh is fast.
2. Enable the view-rewrite variables so that queries against your Logical View are automatically rewritten to use that fast, aligned MV.
Summary
For a JSON-heavy environment, these variables are the "glue" that allows you to have a clean, user-friendly Logical View layer without sacrificing the massive performance gains of an underlying Materialized View. If you see the rewrite already working occasionally, enabling these will make that behavior significantly more consistent and performant.
References
• sql-reference/System_variable.md
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.mdCliff Resnick
04/20/2026, 5:34 PMRocky
04/20/2026, 5:35 PMmv_minute directly against the base table would significantly reduce the risk of deadlocks and improve the stability of your system.
The "deadlock" you are experiencing in the planner is almost certainly a Locking Dependency Conflict between the MV refresh process and the Query Rewrite engine. Here is why the current structure is problematic and how simplifying it solves the issue:
1. The Locking Conflict (Why Deadlocks Happen)
When you have mv_hour -> mv_minute -> logical_view -> base_table, StarRocks has to manage multiple types of metadata locks simultaneously:
* The Refresh Thread: When mv_minute or mv_hour refreshes, it takes a Write Lock on the MV and a Read Lock on its parents (the view or the other MV).
* The Planner (Query Rewrite): When a user queries the logical_view, the optimizer takes a Read Lock on the view. To check if it can rewrite the query to an MV, it then tries to acquire Read Locks on all candidate MVs (mv_minute, mv_hour).
* The Deadlock: If these locks are acquired in a different order (e.g., Refresh locks $A \rightarrow B$, while Rewrite locks $B \rightarrow A$), the FE (Frontend) can hang. This is especially common if your views and MVs are in different databases, as StarRocks must lock databases in a specific alphabetical order to avoid exactly this.
2. View Expansion vs. MV Matching
When an MV is built on a logical_view, the planner faces a "double-ended" problem:
1. It must expand the logical_view to understand the underlying JSON shredding.
2. It must then match that expanded logic against the mv_minute definition.
3. If mv_minute also depends on that same view, the metadata dependency graph is circular or highly redundant. This causes the optimizer to traverse the same metadata nodes multiple times, increasing the chance of hitting a lock held by a background refresh.
3. Why Building Against the Base Table is Better
If you change to `mv_minute -> base_table`:
* Flat Dependencies: The refresh process only needs to lock the base_table and the mv_minute. It completely ignores the logical_view metadata.
* Atomic Rewrite: When a user queries the logical_view, the optimizer sees the base_table at the bottom. It can then "jump" directly to mv_minute because the mv_minute is a known pre-aggregation of that same base_table.
* Partition Alignment: As we discussed before, alignment is much more likely to "stick" when the source is an OlapTable rather than a LogicalView.
4. Is mv_hour -> mv_minute still okay?
Yes, building an MV on another MV (Nested MVs) is a supported and common pattern. However, the source of the chain should be the base table whenever possible.
Recommended Revised Architecture:
1. mv_minute: Point this directly at the JSON Base Table. (Partitioned by Hour, Aligned).
2. mv_hour: Point this at mv_minute. (Partitioned by Hour, Aligned).
3. logical_view: Keep this as your "shredded" user-facing layer on the Base Table.
4. Rewrite: Enable enable_view_based_mv_rewrite.
The result:
* User Query: SELECT * FROM logical_view $\rightarrow$ Optimizer sees shredding $\rightarrow$ Matches to mv_minute $\rightarrow$ Query is fast.
* System Refresh: base_table $\rightarrow$ mv_minute $\rightarrow$ mv_hour. The metadata paths are clean, linear, and much less likely to deadlock.
Pro-Tip for Troubleshooting
If the deadlock happens again, run show processlist on the leader FE. If you see a thread in the Holding Lock or Waiting for Lock state, you can check the fe.log for "DeadlockChecker" entries. StarRocks (v3.1.6+) has a built-in detector that will print the exact lock conflict to the log, which would confirm if the logical_view is indeed the "middle-man" causing the circular wait.
ReferencesCliff Resnick
04/21/2026, 4:04 PMmv_minute to read from base table. But as soon as I create mv_hour -> mv_minute the planner deadlocks when query rewrite is enabled. If I create mv_hour with month partition it does not map to `mv_minute`'s partitions, but there is no planner deadlock. I tried checking FE logs but found no relevant info.Rocky
04/21/2026, 4:04 PMmv_hour $\rightarrow$ mv_minute $\rightarrow$ base_table, and both MVs share the same partition granularity (Hour), the optimizer attempts to prove that the data is consistent across the entire chain for every single partition.
1. Why the "Deadlock" occurs
It is likely not a traditional database deadlock (two threads waiting for each other), but rather a State-Space Explosion or Recursive Validation Loop in the planner:
* Recursive Mapping: When the query against the base_table (from the view) is being planned, the optimizer finds mv_minute. It then sees mv_hour is built on mv_minute.
* The Hour-to-Hour Trap: Because the partition grains are identical, the optimizer tries to build a "Partition Mapping Table" for the entire chain. If you have many partitions (e.g., months of hourly data), the number of permutations the optimizer checks to ensure "Partition $X$ in mv_hour matches Partition $X$ in mv_minute which matches Partition $X$ in `base_table`" can cause the FE to hang or hit a CPU loop.
* Why Monthly works: When mv_hour is Monthly, the optimizer immediately recognizes that the partitions do not align 1:1. It simplifies its logic, likely giving up on the granular partition-level rewrite and falling back to a simpler "Full Rewrite" check, which bypasses the loop.
2. How to bypass the Planner Hang
To prove if this is the issue and to get your system stable, try these adjustments in order:
A. Limit the Nesting Level
StarRocks allows you to control how "deep" the optimizer looks for MVs. If you set this to 1, it will see mv_minute but won't try to "double-hop" to mv_hour during the rewrite:
sql
SET GLOBAL nested_mv_rewrite_max_level = 1;
If the deadlock stops, this confirms the "double-hop" partition alignment is the culprit.
B. Disable Partition-Specific Rewrite (Temporarily)
You can test if the alignment logic specifically is failing by disabling the partition-level check:
sql
SET GLOBAL enable_materialized_view_rewrite_for_partition = false;
Note: This might prevent the MV from being used if only some partitions are fresh, but it will stop the planner from hanging.
C. Check the "Active" Status
An MV must be in an active state to be used for rewrite. If mv_minute is inactive but mv_hour is active, the planner might get confused trying to bridge the gap.
sql
SELECT NAME, IS_ACTIVE, REASON FROM information_schema.materialized_views;
3. The "Correct" Architecture for JSON Shredding
If your primary goal is to make the logical_view (shredded JSON) fast, you should actually decouple the MVs from the view's hierarchy:
1. MV 1: mv_minute $\rightarrow$ reads from base_table (Partitioned by Hour).
2. MV 2: mv_hour $\rightarrow$ reads from base_table (Partitioned by Hour/Day).
_ _Why?* Even though mv_hour is an aggregation of minutes, pointing it at the base_table removes the metadata dependency on mv_minute. The StarRocks optimizer is smart enough to use mv_minute to refresh mv_hour internally if it's faster, but the definition is cleaner.
3. The View: Leave the logical_view pointing to the base_table.
4. Rewrite: Enable enable_view_based_mv_rewrite.
Why this stops the deadlock:
The optimizer now only has to match View -> Base Table and then look for MVs on Base Table. It sees two flat candidates (mv_minute and mv_hour) instead of a recursive chain. It will pick the most efficient one (usually mv_hour) without getting stuck in a recursive alignment loop.
Diagnostic StepRocky
04/21/2026, 4:04 PMEXPLAIN on your query. If it hangs even for EXPLAIN, it is 100% a planner loop. If you are on StarRocks v3.2+, check if enable_materialized_view_plan_cache is enabled; sometimes clearing the plan cache (FLUSH PLAN CACHE) can resolve stuck states after a schema change.
Rocky's Summary: The "sometimes" alignment and the "nested deadlock" both suggest that the optimizer is struggling with the depth of your metadata. Flattening the MVs to both point at the base table is the most robust way to support JSON shredding via views.
References
• knowledge/trouble_shooting/fe_deadlock_problems.md
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.mdCliff Resnick
04/22/2026, 5:44 PMmv_minute and mv_hour both created independently from base table (not logical view). However, now mv union rewrite does not happen like it used to when mv_hour was based on mv_minute . I tried using trace logs but I did not understand it.Rocky
04/22/2026, 5:45 PMmv_hour $\to$ mv_minute):
If mv_hour is missing data for the last hour, the optimizer looks at its source (mv_minute). It sees that mv_minute is fresh and "compensates" by unioning mv_hour with a subset of mv_minute. This is why it felt seamless.
* Independent Case (mv_hour $\to$ Base, mv_minute $\to$ Base):
If mv_hour is missing data, the optimizer looks at its source (the Base Table). It sees the base_table is fresh and unions mv_hour with the base_table. It does not recursively check if another sibling MV (mv_minute) can cover that base_table gap unless it performs a second pass of the rewrite, which is often restricted for performance.
2. How to interpret the TRACE LOGS
To confirm this, run TRACE LOGS MV <your_query>;. Look for these specific signals:
* process union rewrite: This block shows the optimizer trying to fill gaps.
* candidate mv: mv_hour: It will show if mv_hour was picked.
* compensation result: Look at what it's unioning. If you see it unioning with the Base Table (OlapScanNode) instead of mv_minute, it proves the "Source Dependency" issue.
* fail to rewrite: If it rejects an MV entirely, look for the reason. Common reasons include partition mapping failed or cannot find compensation.
3. Solving the "Deadlock vs. Union" Dilemma
You want the Union Rewrite (Nested behavior) without the Planner Deadlock. The deadlock usually occurs when the logical_view is part of the MV's DDL, creating a complex dependency loop in the metadata.
The "Clean" Architecture to fix both:
1. DDL Strategy: Point both MVs at the Base Table, but keep the dependency between them.
* mv_minute $\to$ base_table (OlapTable)
* mv_hour $\to$ mv_minute (MaterializedView)
* Crucial: Do not use the logical_view inside your CREATE MATERIALIZED VIEW statements. This prevents the metadata loop.
2. Query Strategy: Point your users at the logical_view.
* logical_view $\to$ base_table.
3. Variable Strategy:
* SET GLOBAL enable_view_based_mv_rewrite = true;
* SET GLOBAL nested_mv_rewrite_max_level = 3; (Ensure this is $>1$)
Why this works:
When a user queries the logical_view, the optimizer expands it to the base_table. It then searches for MVs that match the base_table. It finds mv_minute. It then sees mv_hour is an extension of mv_minute.
* Rewrite: It will now correctly use mv_hour + mv_minute (compensation) for the base_table query.
* Deadlock: Because the MV DDLs only reference the base_table and each other—and not the view—the FE metadata locks stay linear and predictable.
Summary of why your independent MVs don't union
StarRocks prioritizes the best single MV for a query. If that MV is stale for some partitions, it unions with the Base Table. It does not currently "multi-source" a union rewrite by combining two independent siblings (MV A + MV B) to cover a single-satisfy a query against Base Table C.
To get the union behavior back, you must nest the DDLs, but keep those DDLs away from your logical_view to stop the deadlocks from step 1.
References
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.mdRocky
04/22/2026, 5:45 PMRocky
04/22/2026, 8:40 PMsql
SELECT
time_slice(event_timestamp, INTERVAL 1 MINUTE) AS minute_timestamp,
date_trunc('minute', event_timestamp) AS event_timestamp -- < This shadows the base column
When the planner tries to rewrite a query like `SELECT ... FROM base_table WHERE event_timestamp > ...`:
1. It finds mv_minute.
2. It sees that mv_minute has a column called event_timestamp.
3. It then looks at the definition of that column: date_trunc('minute', event_timestamp).
4. Because the alias and the source have the same name, the expression rewriter can get stuck in a recursive loop: it tries to replace the query's event_timestamp with the MV's event_timestamp, but then sees the MV's column is itself a function of event_timestamp.
In a nested scenario (mv_hour -> mv_minute), this complexity doubles. The planner has to resolve date_trunc('hour', date_trunc('minute', base.event_timestamp)) while navigating the name collisions. This is what causes the "deadlock" (actually a CPU hang in the FE).
2. The time_slice vs date_trunc Redundancy
Both time_slice(ts, INTERVAL 1 MINUTE) and date_trunc('minute', ts) return the exact same value (the start of the minute).
* By including both, you are forcing the optimizer to track two different expression trees for the same data.
* If your PARTITION BY uses one and your GROUP BY uses the other, the optimizer has to prove they are equivalent during every query, which is mathematically intensive and prone to bugs in nested rewrites.
3. How to fix it (and keep Union Rewrite)
To get the stability of independent tables with the power of Union Rewrite, you should use distinct naming and consistent functions.
Recommended DDL for `mv_minute`:
sql
CREATE MATERIALIZED VIEW mv_minute
PARTITION BY event_minute -- Use the alias directly
AS
SELECT
-- Use ONE canonical time column for the grain
date_trunc('minute', event_timestamp) AS event_minute,
...
FROM base_table
GROUP BY event_minute, ...;
Recommended DDL for mv_hour (Nested):
sql
CREATE MATERIALIZED VIEW mv_hour
PARTITION BY date_trunc('hour', event_minute) -- Roll up the minute column
AS
SELECT
date_trunc('hour', event_minute) AS event_hour,
sum(metrics)
FROM mv_minute -- Dependency remains for Union Rewrite
GROUP BY event_hour;
Why this solves your issues:
1. No Shadowing: By using event_minute and event_hour, the planner never confuses the MV columns with the base table columns.
2. Linear Expressions: The optimizer sees a clear path: base.event_timestamp $\to$ mv_minute.event_minute $\to$ mv_hour.event_hour.
3. Union Rewrite restored: Because mv_hour is still physically built on mv_minute, the optimizer can use mv_minute to "fill the gaps" if mv_hour hasn't refreshed the latest hour yet.
4. Planner Stability: The "Monthly" partition trick worked for you previously because it simplified the math enough to avoid the hang; fixing the names will allow you to go back to "Hourly" partitions safely.
Rocky's Checklist:
* Check SHOW MATERIALIZED VIEWS and ensure IS_ACTIVE is true.
* Run SET GLOBAL enable_view_based_mv_rewrite = true; to ensure your logical_view can still find these MVs.
* If it still hangs, use EXPLAIN VERBOSE <query> to see where the planner stops.
References
• using_starrocks/async_mv/feature-support-asynchronous-materialized-views.mdCliff Resnick
04/22/2026, 8:47 PMRocky
04/22/2026, 8:47 PMmv_minute by hour while keeping minute-level data, you do not need to shadow the base table column name or provide redundant expressions. The "double timestamp" approach you are using is triggering a recursive loop in the StarRocks optimizer because of the column name shadowing.
1. Why the "Double Timestamp" causes the Deadlock
In your current DDL, you have:
sql
SELECT
time_slice(event_timestamp, INTERVAL 1 MINUTE) AS minute_timestamp,
date_trunc('minute', event_timestamp) AS event_timestamp -- Shadowing
...
PARTITION BY date_trunc('hour', event_timestamp) -- Using the shadowed name
When the planner attempts to rewrite a query against the base table:
1. It sees a filter on base_table.event_timestamp.
2. It looks at mv_minute and sees a column named event_timestamp.
3. It tries to map the filter to the MV, but sees the MV's event_timestamp is defined as date_trunc('minute', base_table.event_timestamp).
4. The optimizer then tries to calculate the partition alignment. It sees PARTITION BY date_trunc('hour', event_timestamp).
5. Because the name is shadowed, the optimizer's internal expression tree becomes: date_trunc('hour', date_trunc('minute', event_timestamp)).
6. When you then nest mv_hour on top of this, the chain of date_trunc calls on shadowed names becomes mathematically "opaque" to the planner, causing it to fall into an infinite search loop (the "deadlock").
2. The Correct Way: Hour Partitioning for Minute Data
You can achieve an hourly partition for a minute-grain MV using a single alias that is NOT the same name as the base column.
Recommended mv_minute DDL:
sql
CREATE MATERIALIZED VIEW mv_minute
PARTITION BY date_trunc('hour', m_ts) -- Partitioned by Hour
REFRESH ASYNC
AS
SELECT
-- Use one name for your minute-grain column
date_trunc('minute', event_timestamp) AS m_ts,
count(*) as cnt,
sum(value) as total
FROM base_table
GROUP BY m_ts;
Recommended mv_hour DDL (Nested):
sql
CREATE MATERIALIZED VIEW mv_hour
PARTITION BY date_trunc('hour', m_ts) -- Aligned with mv_minute
REFRESH ASYNC
AS
SELECT
date_trunc('hour', m_ts) AS h_ts, -- Roll up the minute grain
sum(cnt) as cnt,
sum(total) as total
FROM mv_minute -- Points to the MV, not the base table
GROUP BY h_ts;
3. Why this fixes both problems
1. Stops the Deadlock: By using m_ts instead of event_timestamp, you break the shadowing loop. The optimizer can clearly see m_ts is a derivative of the base column without getting confused by self-referencing names.
2. Restores Union Rewrite: Because mv_hour is explicitly defined as FROM mv_minute, the optimizer's Partition Compensation logic knows exactly how to "fill the gap" if mv_hour is slightly behind mv_minute.
3. Partition Alignment: Since both MVs are now effectively "Hour-partitioned" (one contains 60 minute-rows per partition, the other contains 1 hour-row per partition), the optimizer can easily map them 1:1.
4. Setting the View for Transparent Rewrite
Now, point your logical_view back to the base table so users get the benefit of the MVs automatically:
sql
CREATE VIEW logical_view AS
SELECT
date_trunc('minute', event_timestamp) AS event_timestamp,
...
FROM base_table;
Ensure these session variables are set:
* SET GLOBAL enable_view_based_mv_rewrite = true; (Allows the view to be rewritten by MVs).
* SET GLOBAL nested_mv_rewrite_max_level = 3; (Allows View -> mv_minute -> mv_hour chain).
Summary: The deadlock was a "Name Collision Loop." By using a unique alias like m_ts for your data grain and referencing that alias in your PARTITION BY date_trunc('hour', m_ts), you satisfy the hour-partition requirement without breaking the optimizer.
References
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.mdCliff Resnick
04/22/2026, 8:55 PMdate_trunc('minute', event_timestamp) AS event_minute
and my partition clause is
PARTITION BY (date_trunc('hour', event_minute))
I then get this error
ERROR 1064 (HY000): Getting analyzing error from line 37, column 14 to line 37, column 45. Detail message: Materialized view partition function date_trunc must related with column.Rocky
04/22/2026, 8:55 PMPARTITION BY clause in asynchronous Materialized Views is quite strict. It expects the argument inside the partition function (like date_trunc) to be a direct reference to a base table column or a simple alias, and it often fails when you try to nest functions or apply a function to an alias that is already an expression.
The extra parentheses PARTITION BY (date_trunc(...)) are also not part of the standard syntax for async MVs and can trigger parsing errors.
The Solution: The "Clean Double Timestamp"
To achieve Hour-level partitioning for Minute-level data without triggering the "must relate to column" error or the "planner deadlock," you should define both time grains as distinct aliases in your SELECT list and partition by the higher grain alias directly.
Corrected DDL for `mv_minute`:
sql
CREATE MATERIALIZED VIEW mv_minute
-- 1. Use the alias directly without extra parentheses
PARTITION BY event_hour
REFRESH ASYNC
AS
SELECT
-- 2. Define both grains with UNIQUE names (no shadowing)
date_trunc('minute', event_timestamp) AS event_minute,
date_trunc('hour', event_timestamp) AS event_hour,
count(*) as cnt
FROM base_table
-- 3. Group by both to ensure they are available for partitioning
GROUP BY event_minute, event_hour;
Why this fixes the error:
1. Direct Column Mapping: By defining event_hour in the SELECT list, StarRocks treats it as a "column" of the MV. The PARTITION BY event_hour clause then identifies this column as the partitioning key.
2. Avoids Parsing Ambiguity: StarRocks no longer has to evaluate date_trunc('hour', ...) inside the partition analyzer; it simply looks at the event_hour alias definition in the SELECT list to understand the relationship to the base table.
3. No Shadowing: By using event_minute and event_hour (instead of event_timestamp), you avoid the recursive loop that causes the planner to hang/deadlock.
Will this still allow Union Rewrite and Query Rewrite?
* Query Rewrite: Yes. If a user queries SELECT date_trunc('minute', event_timestamp)..., the optimizer will correctly match it to the event_minute column in the MV.
* Union Rewrite: Yes. Since the MV is now successfully partitioned by event_hour, StarRocks can perform partition-level tracking. If the base table has a new hour of data that isn't in the MV yet, the optimizer will use the MV for the old data and the base table for the new hour.
* Nesting: You can now safely build mv_hour on top of mv_minute by using FROM mv_minute and GROUP BY event_hour.
Summary of what to change:
1. Remove the parentheses from your PARTITION BY clause.
2. Reference the alias event_hour instead of calling date_trunc again inside the partition clause.
3. Ensure both event_minute and event_hour are in your SELECT and GROUP BY blocks.
Rocky's Tip: If you are on a version earlier than 3.2, StarRocks is even more restrictive about partition expressions. Using the "Alias in SELECT + Partition by Alias" pattern is the most compatible way across all versions.
References
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.md
• using_starrocks/async_mv/feature-support-asynchronous-materialized-views.md
• using_starrocks/async_mv/use_cases/data_modeling_with_materialized_views.mdRocky
04/23/2026, 1:45 PMMaterialized view partition function date_trunc must related with column is likely because of how StarRocks handles the relationship between the PARTITION BY clause and the SELECT list.
The Fix: Reference the Base Column in the Partition Clause
StarRocks requires the PARTITION BY expression to be able to map back to a physical column in the base table to manage the incremental refresh. When you use PARTITION BY date_trunc('hour', event_minute), the analyzer sees an expression acting on an alias, which is itself an expression, and it loses the "lineage" to the base column.
Use this DDL structure:
sql
CREATE MATERIALIZED VIEW mv_minute
-- 1. Point the partition function DIRECTLY to the base table column
PARTITION BY date_trunc('hour', event_timestamp)
REFRESH ASYNC
AS
SELECT
-- 2. Keep your unique aliases here
date_trunc('minute', event_timestamp) AS event_minute,
date_trunc('hour', event_timestamp) AS event_hour,
sum(clicks) as total_clicks
FROM base_table
-- 3. Group by both aliases
GROUP BY event_minute, event_hour;
Why this works:
1. No Deadlock: Because you are not using the name event_timestamp as an alias in the SELECT list, the planner's rewrite engine doesn't get stuck in a recursive loop.
2. No Syntax Error: By using date_trunc('hour', event_timestamp) in the PARTITION BY clause, you are providing a direct "lineage" that StarRocks understands. It knows that one partition in this MV (an hour) corresponds to data in the base table for that same hour.
3. Minute Grain Preserved: Your MV will still have 60 rows per partition (one for each event_minute), but they will all be physically stored inside an hourly partition bucket. This is exactly what you wanted for performance and TTL management.
Pro-Tip for your Nested mv_hour
Now that mv_minute is safely partitioned by hour, you can create your mv_hour on top of it very cleanly:
sql
CREATE MATERIALIZED VIEW mv_hour
-- Since mv_minute is partitioned by hour, this is a 1-to-1 partition mapping
PARTITION BY event_hour
REFRESH ASYNC
AS
SELECT
event_hour,
sum(total_clicks) as total_clicks
FROM mv_minute
GROUP BY event_hour;
Key takeaways to avoid the planner hang:
* Never name an MV column the same as a base table column if that MV column is an expression of the base column.
* Always reference the base table's partition column in the MV's PARTITION BY date_trunc(...) clause.
* Unique Aliases (like event_minute and event_hour) are your friend; they make the execution plan much easier for the StarRocks optimizer to "reason" about.
References
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.md
• using_starrocks/async_mv/feature-support-asynchronous-materialized-views.md
• sql-reference/sql-statements/materialized_view/CREATE_MATERIALIZED_VIEW.md
-VIEW)
• table_design/data_distribution/expression_partitioning.md
References
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.md
• best_practices/partitioning.md
• using_starrocks/async_mv/use_cases/data_modeling_with_materialized_views.mdCliff Resnick
04/23/2026, 1:54 PMmv_minute with
SELECT
date_trunc('minute', event_timestamp) AS event_minute,
date_trunc('hour', event_timestamp) AS event_hour,
PARTITION BY event_hour
and mv_hour with
SELECT
event_hour as partition_hour,
date_trunc('day', event_hour) as event_day,
PARTITION BY event_day
Note that I renamed event_hour in mv_hour to make sure there was no possibility of shadowing. But this causes the planner lockup. I think it may not be possible to have a nested materialized view with this partitioning.
Since I did once have it working with only intermittent lockups, using time_slice for aggregate granularity field and and date_trunc for partitioning field, I am wondering if there is somehow some bad metadata stuck in the system. If so then how do I purge?Rocky
04/23/2026, 1:55 PMdate_trunc or time_slice functions across multiple MV levels to prove they are equivalent to the base table.
Here is how to fix the DDL to avoid the lockup and how to "purge" the system if you suspect bad metadata.
1. The "Clean" Nested DDL Pattern
To avoid the planner lockup, you must ensure the PARTITION BY clause uses the explicit function expression rather than the alias, and that you use unique names for every time grain across all levels.
Level 1: mv_minute (Partitioned by Hour)
Use the base table column inside the partition function. This provides a direct "lineage" for the optimizer.
sql
CREATE MATERIALIZED VIEW mv_minute
PARTITION BY date_trunc('hour', event_timestamp) -- Use base column here
REFRESH ASYNC
AS
SELECT
date_trunc('minute', event_timestamp) AS m_ts, -- Aggregation grain
date_trunc('hour', event_timestamp) AS h_ts, -- Partition grain
count(*) as cnt
FROM base_table
GROUP BY m_ts, h_ts;
Level 2: mv_hour (Partitioned by Day)
When nesting, reference the partitioning column of the parent MV (h_ts).
sql
CREATE MATERIALIZED VIEW mv_hour
PARTITION BY date_trunc('day', h_ts) -- Roll up the parent's partition key
REFRESH ASYNC
AS
SELECT
h_ts,
date_trunc('day', h_ts) AS d_ts,
sum(cnt) as cnt
FROM mv_minute -- Points to Level 1
GROUP BY h_ts, d_ts;
2. Why the Lockup Happens
The optimizer's rewrite engine has a set of rules to match expressions. When it sees:
date_trunc('day', date_trunc('hour', event_timestamp)) (from Level 2) vs
date_trunc('day', event_timestamp) (from a query)
It tries to "unfold" these. If you use aliases that the optimizer then re-expands into their underlying expressions, it can create a recursive search tree that never terminates.
* Shadowing Fix: Using m_ts, h_ts, and d_ts instead of reusing event_timestamp or event_hour is the correct first step.
* Parentheses/Alias Fix: Always use PARTITION BY date_trunc('unit', col) without extra parentheses or just the bare alias if the alias is exactly the function you want.
3. How to Purge "Bad Metadata" or Stuck States
If the planner is still locking up after you've fixed the DDL, it might be due to the Materialized View Plan Cache or stale metadata in the FE (Frontend).
1. Drop and Recreate (Metadata Purge):
StarRocks does not have a "purge" command for MVs. The only way to clear the metadata for an MV is to drop it.
sql
DROP MATERIALIZED VIEW IF EXISTS mv_hour;
DROP MATERIALIZED VIEW IF EXISTS mv_minute;
2. Clear the Optimizer Cache (Session level):
If the planner is hanging, try disabling the rewrite cache in your current session to see if it resolves:
sql
SET enable_mv_query_context_cache = false; -- v3.3+
SET mv_plan_cache_max_size = 0; -- v3.2+
3. Check for "Ghost" Tasks:
Sometimes a background refresh task is stuck.
sql
SELECT * FROM information_schema.task_runs WHERE state = 'RUNNING';
-- If stuck, cancel the task
CANCEL TASK task_name;
4. Force Re-analysis:
After recreating the MVs, run a manual refresh with FORCE to ensure the partition mapping is freshly calculated:
sql
REFRESH MATERIALIZED VIEW mv_minute FORCE;
Summary of Recommended Strategy
The "double timestamp" (one for grouping, one for partitioning) is actually the standard best practice for this scenario. The lockup is purely an optimizer search-space issue. By using the Level 1 Col -> Level 2 Partition pattern (as shown in the Level 2 DDL above) and ensuring no column name is ever the same as the base table's column, you should resolve the lockup.Rocky
04/23/2026, 1:55 PMCliff Resnick
04/23/2026, 2:05 PMERROR 1064 (HY000): Getting analyzing error. Detail message: Materialized view partition exp column:event_timestamp is not found in query statement.
sql
CREATE MATERIALIZED VIEW mv_minute
PARTITION BY date_trunc('hour', event_timestamp) -- Use base column here
REFRESH ASYNC
AS
SELECT
date_trunc('minute', event_timestamp) AS m_ts, -- Aggregation grain
date_trunc('hour', event_timestamp) AS h_ts, -- Partition grain
count(*) as cnt
FROM base_table
GROUP BY m_ts, h_ts;Rocky
04/23/2026, 2:06 PMMaterialized view partition function date_trunc must related with column and the subsequent error column:event_timestamp is not found in query statement are due to a very specific requirement in StarRocks: The column used in the PARTITION BY expression must be present in the SELECT list of the materialized view.
However, because you are using a GROUP BY clause, any column in your SELECT list must also be in your GROUP BY list. This creates a conflict when you want an aggregation grain (minute) that is different from your partition grain (hour).
1. The Correct DDL for mv_minute
To achieve Minute-level data stored in Hour-level partitions, you should define two distinct time columns in your SELECT list and use the coarser one as your partition key.
sql
CREATE MATERIALIZED VIEW mv_minute
-- Use the alias name directly, WITHOUT extra parentheses
PARTITION BY event_hour
REFRESH ASYNC
AS
SELECT
date_trunc('minute', event_timestamp) AS event_minute, -- Aggregation grain
date_trunc('hour', event_timestamp) AS event_hour, -- Partition grain
count(*) as cnt
FROM base_table
-- Group by BOTH to ensure both are "available" for the partition clause
GROUP BY event_minute, event_hour;
Why this works:
* By including event_hour in the GROUP BY, it becomes a valid column for the PARTITION BY clause.
* Because event_minute is a finer grain than event_hour, grouping by both does not change your row count; you still get one row per minute.
* Using the alias event_hour directly in PARTITION BY avoids the parser error you saw when trying to nest date_trunc on an alias.
──────────
2. Fixing the Nested "Planner Lockup"
The "planner lockup" (hang/infinite loop) is a known issue in some StarRocks versions when the optimizer tries to mathematically "rollup" nested date_trunc functions (e.g., trying to prove that day(hour(timestamp)) is the same as day(timestamp)).
To break the loop in your nested `mv_hour`:
1. Avoid Redundant `date_trunc`: In your second MV, instead of applying date_trunc('day', event_hour) to a column that is already truncated, try using a simple CAST.
2. Partition by the parent grain: If mv_hour is already very small, consider partitioning it by the same grain as its parent (event_hour) to avoid the complex rollup logic.
3. Clear the Planner Cache: If you suspect "stuck" logic, run these commands in your session to bypass the optimizer's cache:
sql
SET mv_plan_cache_max_size = 0;
SET enable_mv_query_context_cache = false;
Optimized mv_hour DDL:
sql
CREATE MATERIALIZED VIEW mv_hour
PARTITION BY event_day -- Or just partition by h_ts to avoid the rollup logic
REFRESH ASYNC
AS
SELECT
event_hour AS h_ts,
date_trunc('day', event_hour) AS event_day,
sum(cnt) as total_cnt
FROM mv_minute
GROUP BY h_ts, event_day;
3. How to "Purge" the System
If you still experience intermittent lockups, StarRocks doesn't have a "purge metadata" command for MVs other than DROP. However, you can ensure a clean state by:
1. Dropping the MVs in reverse order (top-level first).
2. Checking for orphaned tasks: SELECT * FROM information_schema.task_runs WHERE state = 'RUNNING'; and using CANCEL TASK <id>; if any are stuck.
3. Manual Refresh with Force: After recreating, run REFRESH MATERIALIZED VIEW mv_minute FORCE; to rebuild the partition mappings from scratch.
Rocky's Tip: If the lockup persists, try setting SET enable_materialized_view_rewrite = false; for the session where you create/refresh the MV. This will disable the "Query Rewrite" feature but will allow the MV to build and refresh its data without the optimizer getting stuck in a loop.
References
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.mdRocky
04/23/2026, 2:06 PMCliff Resnick
04/23/2026, 2:33 PMmv_minute partitions completed but mv_hour refresh run continues to be SKIPPED. When will it start actually refreshing?Rocky
04/23/2026, 2:33 PMmv_hour is skipping while mv_minute is completing, it usually boils down to one of the following reasons:
1. The "Wait for Whole Partition" Logic
If mv_hour is partitioned by Day and mv_minute is partitioned by Hour, StarRocks needs to map the dependency.
* By default, StarRocks tracks changes at the partition level. When an hour partition in mv_minute updates, the corresponding day partition in mv_hour becomes "stale."
* However, if you have properties like partition_refresh_number set (which defaults to 1 in v3.x), the scheduler might be waiting for more data or a specific trigger condition before it decides to process that day.
* The Fix: Check if mv_hour eventually starts once the first full "Day" worth of hours in mv_minute is finished.
2. Partition Alignment Issues (The "Rollup" Problem)
For StarRocks to automatically refresh a nested MV, the partition key of the child (mv_hour) must be a clear mathematical rollup of the parent (mv_minute).
* In your previous attempt, you used PARTITION BY event_day where event_day was an alias for date_trunc('day', event_hour).
* If the optimizer doesn't "trust" that event_day in mv_hour maps perfectly to the hours in mv_minute, it may fail to find the delta.
* The Fix: Try a manual refresh once to "link" the versions:
sql
REFRESH MATERIALIZED VIEW mv_hour FORCE;
The FORCE flag ignores the version check and forces a recalculation, which often resets the metadata tracking.
3. Version Tracking "Stuck" (Metadata Purge)
Since you mentioned "bad metadata," it is possible the system thinks mv_hour is already consistent with the new mv_minute because the internal version numbers (visible in SHOW TABLE STATUS) happen to align or weren't reset.
* The "Purge" Method:
1. DROP MATERIALIZED VIEW mv_hour;
2. DROP MATERIALIZED VIEW mv_minute;
3. Clear the task history (optional but helpful for peace of mind): DELETE FROM information_schema.task_runs WHERE task_name LIKE 'mv_hour%'; (Note: You can't actually delete from this system table, but you can filter your view of it).
4. Recreate mv_minute and wait for it to finish at least one partition.
5. Recreate mv_hour.
4. Check the "Extra Message" for the real reason
Run this query to see exactly why the scheduler decided to skip:
sql
SELECT
task_name,
create_time,
state,
extra_message
FROM information_schema.task_runs
WHERE task_name LIKE '%mv_hour%'
ORDER BY create_time DESC LIMIT 5;
Look at the extra_message column. It will often contain a JSON string like:
* "refBasePartitionsToRefreshMap":{} → This means it literally found 0 partitions that it thinks need updating.
* "forceRefresh":false → It’s doing a standard check and failing to find a delta.
Summary: When will it start?
If you don't want to wait, run REFRESH MATERIALIZED VIEW mv_hour FORCE;. This is the standard way to "kick" the system into realizing the world has changed. Once a FORCE refresh completes successfully, the automatic ASYNC logic usually picks up correctly for subsequent increments.
Rocky's Tip: If you are on StarRocks v3.2 or v3.3, check the property partition_refresh_number. If it's set to 1 (the new default), it will refresh one partition at a time. If your mv_hour has no partitions yet (newly created), it might be confused about where to start. Running a manual REFRESH ... PARTITION START ("2023-01-01") END ("2023-01-02") can help define the boundaries.
References
• using_starrocks/async_mv/materialized_view_task_run_details.mdRocky
04/23/2026, 2:33 PMCliff Resnick
04/23/2026, 3:09 PM22ms| [MV TRACE] [PREPARE mv_trader_delivery_by_hour] Failed to get mv timeliness info: com.starrocks.common.AnalysisException: Can't find base table mv_trader_delivery_by_minute from baseToMvNameRef |
| at com.starrocks.catalog.mv.MVTimelinessArbiter.getMVToRefreshPartitionNames(MVTimelinessArbiter.java:215) |
| at com.starrocks.catalog.mv.MVTimelinessRangePartitionArbiter.getMVTimelinessUpdateInfoInChecked(MVTimelinessRangePartitionArbiter.java:144) |
| at com.starrocks.catalog.mv.MVTimelinessArbiter.getMVTimelinessUpdateInfo(MVTimelinessArbiter.java:153) |
| at com.starrocks.catalog.MvRefreshArbiter.getMVTimelinessUpdateInfo(MvRefreshArbiter.java:93) |
| at com.starrocks.scheduler.mv.MVTimelinessMgr.getMVTimelinessInfo(MVTimelinessMgr.java:51) |
| at com.starrocks.sql.optimizer.QueryMaterializationContext.getOrInitMVTimelinessInfos(QueryMaterializationContext.java:243) |
| at com.starrocks.sql.optimizer.MvRewritePreprocessor.prepareRelatedMVs(MvRewritePreprocessor.java:755) |
| at com.starrocks.sql.optimizer.MvRewritePreprocessor.prepare(MvRewritePreprocessor.java:173) |
| at com.starrocks.sql.optimizer.QueryOptimizer.prepareMvRewrite(QueryOptimizer.java:342) |
| at com.starrocks.sql.optimizer.QueryOptimizer.optimize(QueryOptimizer.java:198) |
| at com.starrocks.sql.StatementPlanner.createQueryPlanWithReTry(StatementPlanner.java:404) |
| at com.starrocks.sql.StatementPlanner.plan(StatementPlanner.java:154) |
| at com.starrocks.sql.StatementPlanner.plan(StatementPlanner.java:108) |
| at com.starrocks.qe.StmtExecutor.generateExecPlan(StmtExecutor.java:710) |
| at com.starrocks.qe.StmtExecutor.execute(StmtExecutor.java:812) |
| at com.starrocks.qe.ConnectProcessor.executeQueryAttempt(ConnectProcessor.java:535) |
| at com.starrocks.qe.ConnectProcessor.runWithParserStageRetry(ConnectProcessor.java:432) |
| at com.starrocks.qe.ConnectProcessor.handleQuery(ConnectProcessor.java:369) |
| at com.starrocks.qe.ConnectProcessor.dispatch(ConnectProcessor.java:746) |
| at com.starrocks.qe.ConnectProcessor.processOnce(ConnectProcessor.java:1123) |
| at com.starrocks.mysql.nio.MySQLReadListener.handleRequest(MySQLReadListener.java:152) |
| at com.starrocks.mysql.nio.MySQLReadListener.lambda$handleEvent$0(MySQLReadListener.java:92) |
| at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) |
| at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) |
| at java.base/java.lang.Thread.run(Thread.java:840)Rocky
04/23/2026, 3:09 PMAnalysisException: Can't find base table ... from baseToMvNameRef indicates that the StarRocks Frontend (FE) has lost the internal mapping between your nested Materialized Views.
This usually happens in nested MV scenarios when the "child" MV (by_hour) references the "parent" MV (by_minute), but the FE's internal dependency tracker fails to register the parent as a valid source. This can be triggered by rapid drop/recreate cycles or if the names are not fully qualified.
1. How to Fix the "AnalysisException"
This error is often a side effect of the FE trying to optimize partition refreshes (the "Timeliness Arbiter"). Try these steps to restore the relationship:
* Fully Qualify Names: Even if you are in the same database, explicitly use the database name in the FROM clause of your child MV. This helps the Arbiter resolve the name to the correct ID.
sql
-- In mv_hour definition:
FROM my_database.mv_trader_delivery_by_minute
* Force Reactivation: Sometimes an MV is "Active" but its internal dependency metadata is stale. Run this for both:
sql
ALTER MATERIALIZED VIEW mv_trader_delivery_by_minute ACTIVE;
ALTER MATERIALIZED VIEW mv_trader_delivery_by_hour ACTIVE;
* Verify Relationships: Run this internal function to see what the FE currently "sees" in its mapping:
sql
SELECT inspect_mv_relationships();
SELECT * FROM sys.object_dependencies WHERE object_name = 'mv_trader_delivery_by_hour';
2. How to "Purge" the System (Metadata Cleanup)
If the system is "stuck" (skipping refreshes or throwing AnalysisExceptions), you need to perform a clean sweep of the metadata. StarRocks doesn't have a single PURGE command, so follow this sequence:
1. Drop in Reverse Order:
sql
DROP MATERIALIZED VIEW IF EXISTS mv_trader_delivery_by_hour;
DROP MATERIALIZED VIEW IF EXISTS mv_trader_delivery_by_minute;
2. Clear Optimizer Caches (Session Level):
These variables force the planner to ignore any cached plans that might be causing the "lockup" or loop:
sql
SET mv_plan_cache_max_size = 0;
SET enable_mv_query_context_cache = false;
3. Clean Up "Ghost" Tasks:
Check for any background refresh tasks that might still be logically tied to the old IDs:
sql
-- Check for running/stuck tasks
SELECT * FROM information_schema.task_runs WHERE state = 'RUNNING';
-- Cancel them if found
CANCEL TASK <task_name>;
4. Recreate with Unique Aliases:
When you recreate, ensure the alias names in the SELECT list are unique across all levels (e.g., minute_grain, hour_grain, day_grain) to prevent the optimizer from getting confused during the "Range Partition Arbiter" check.
3. Why mv_hour is still SKIPPED
If the refresh is skipped after you fixed the DDL:
* Reason: The system thinks the child (hour) is already consistent with the parent (minute). If you just recreated minute, it might have no data yet, or its version is "0," which the child sees as "nothing to do."
* Fix: You must manually kickstart the dependency chain.
1. Wait for mv_minute to finish at least one refresh successfully.
2. Run: REFRESH MATERIALIZED VIEW mv_trader_delivery_by_hour FORCE;
The FORCE keyword is the "purge" for the refresh logic—it ignores the version check and forces the child to re-read everything from the parent.
Recommended Pattern to Avoid Lockup
To prevent the "Planner Lockup" (infinite recursion), use the CAST or date_trunc on the already truncated column from the parent, rather than re-truncating the original timestamp:
sql
-- Parent: mv_minute
SELECT date_trunc('minute', ts) as m_ts, date_trunc('hour', ts) as h_ts ...
GROUP BY m_ts, h_ts;
-- Child: mv_hour
SELECT h_ts, date_trunc('day', h_ts) as d_ts ... -- Reference h_ts, not the original ts
FROM mv_minute
GROUP BY h_ts, d_ts;Rocky
04/23/2026, 3:09 PMMVTimelinessArbiter to track than a nested function call.
References
• using_starrocks/async_mv/materialized_view_task_run_details.md
• sql-reference/sql-functions/meta-functions/inspect_mv_relationships.md
• sql-reference/sql-functions/meta-functions/inspect_related_mv.md
• sql-reference/sys/object_dependencies.md
• using_starrocks/async_mv/mv_ref.md
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.md
• using_starrocks/Materialized_view-single_table.mdCliff Resnick
04/23/2026, 3:29 PM30ms| [MV TRACE] [REWRITE mv_trader_delivery_by_hour] Find a MV compensation: state=NO_COMPENSATE |
| 30ms| [MV TRACE] [REWRITE TF_MV_AGGREGATE_SCAN_RULE] [InMemo:false] [mv_trader_delivery_by_hour] Get compensation predicates:equalPredicates=true, rangePredicates=222: event_minute >= 2026-03-28 00:00:00 AND 222: event_minute < 2026-03-28 15:00:00, residualPredicates=true, isTransparentRewrite: false |
| 30ms| [MV TRACE] [REWRITE mv_trader_delivery_by_hour] Rewrite scalar operator failed: 222: event_minute >= 2026-03-28 00:00:00 cannot be rewritten |
| 30ms| [MV TRACE] [REWRITE TF_MV_AGGREGATE_SCAN_RULE] [InMemo:false] [mv_trader_delivery_by_hour] rewrite predicates from query to mv failed. isMVBased:false, useEc:true, predicate:222: event_minute >= 2026-03-28 00:00:00 AND 222: event_minute < 2026-03-28 15:00:00 |
| 30ms| [MV TRACE] [REWRITE TF_MV_AGGREGATE_SCAN_RULE] [InMemo:false] [mv_trader_delivery_by_hour] Rewrite other predicates compensation failed: 222: event_minute >= 2026-03-28 00:00:00 AND 222: event_minute < 2026-03-28 15:00:00 |
| 30ms| [MV TRACE] [REWRITE TF_MV_AGGREGATE_SCAN_RULE] [InMemo:false] [mv_trader_delivery_by_hour] Success to convert query compensation predicates to MV but rewrite compensation failed |
| 30ms| [MV TRACE] [REWRITE TF_MV_AGGREGATE_SCAN_RULE] [InMemo:false] [mv_trader_delivery_by_hour] doRewrite phase failed |
| 30ms| [MV TRACE] [REWRITE TF_MV_AGGREGATE_TIME_SERIES_SCAN_RULE] [InMemo:false] MV Candidates: [mv_trader_delivery_by_hour] |
| 30ms| [MV TRACE] [REWRITE mv_trader_delivery_by_hour] Find a MV compensation: state=NO_COMPENSATE |
| 30ms| [MV TRACE] [REWRITE mv_trader_delivery_by_hour] Find a MV compensation: state=NO_COMPENSATE |
| 30ms| [MV TRACE] [REWRITE TF_MV_AGGREGATE_TIME_SERIES_SCAN_RULE] [InMemo:false] Query Compensate partition predicate:true |
| 30ms| [MV TRACE] [REWRITE TF_MV_AGGREGATE_TIME_SERIES_SCAN_RULE] [InMemo:false] [mv_trader_delivery_by_hour] doRewrite phase failed |
| 30ms| [MV TRACE] [PREPARE GLOBAL] is further partition prune: false |
| 31ms| Query has already been successfully rewritten by: mv_trader_delivery_by_minute. |
|Rocky
04/23/2026, 3:30 PMmv_hour for the Union rewrite. Even though you fixed the metadata and partitioning, the optimizer is rejecting mv_hour because it cannot "map" the query's filter to the data inside that specific Materialized View.
Why mv_hour is being rejected
The core issue is revealed in these lines:
[REWRITE mv_trader_delivery_by_hour] Rewrite scalar operator failed: 222: event_minute >= ... cannot be rewritten
1. Column Mismatch: Your query likely filters on event_minute (or a timestamp that maps to it). mv_trader_delivery_by_hour, by definition, has aggregated your data up to the hour grain. It no longer contains the event_minute column.
2. Compensation Failure: When the optimizer tries to rewrite the query using mv_hour, it looks for the columns used in your WHERE clause. Since event_minute is missing in mv_hour, it tries to "compensate" (calculate) that predicate using what's available. It fails because it cannot guarantee that an hour-level bucket can satisfy a minute-level filter exactly.
3. Winner Selected: The last line of your trace confirms that mv_trader_delivery_by_minute successfully rewrote the query. Because mv_minute contains the event_minute column, it is a "better" match for the optimizer.
How to fix the Union Rewrite selection
If you want the optimizer to choose mv_hour (to save on scan costs) when the query range allows it, you need to align the query and the MV's capabilities:
1. Add the filter column to the MV (If possible)
If your query always filters by event_minute, but you want to use the smaller mv_hour table, you could technically include event_minute in the mv_hour GROUP BY, but that would defeat the purpose of the hour-level aggregation (it would just be another minute-level MV).
2. Query at the Hour Grain
If you change your query to filter and group by the hour column (event_hour), the optimizer will see that mv_hour is the most efficient source:
sql
-- Query this way to trigger mv_hour rewrite
SELECT event_hour, sum(cnt)
FROM base_table
WHERE event_timestamp >= '2026-03-28 00:00:00'
GROUP BY event_hour;
3. Why Union Rewrite is "Skipping" mv_hour
"Union Rewrite" specifically happens when:
* Some partitions are refreshed in the MV.
* Some partitions are "stale" (new data in base table).
* The optimizer decides to take the "Fresh" data from the MV and "Union" it with the "New" data from the base table.
In your case, since mv_minute is also available and covers the event_minute predicate perfectly, the optimizer prefers it over a complex union involving mv_hour + base table.
Summary of "Scalar Operator Failed"
This error is the optimizer's way of saying: "I see you want to filter by minutes, but this MV only knows about hours. I can't safely use this MV for this specific filter."
The Fix:
If you want to use mv_hour for query acceleration, ensure your queries are written to use the columns present in that MV (the hour grain). If your query must have minute-level precision, mv_minute is the correct and only view the system can safely use.
Rocky's Tip: To see if the Union Rewrite is working at all, try querying a range where you know some hours are refreshed in mv_hour and some are not, and ensure your query uses date_trunc('hour', ...) in the SELECT and WHERE clauses. If you use event_minute, StarRocks will always fall back to mv_minute or the base table to ensure data accuracy.
References
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.md
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.mdRocky
04/23/2026, 3:30 PM