StarRocks' asynchronous materialized view uses a widely adopted transparent query rewrite algorithm based on the SPJG (select-project-join-group-by) form. Without the need to modify the query statement, StarRocks can automatically rewrite queries against the base tables into queries against the corresponding materialized view that contains the pre-computed results. As a result, materialized views can help you significantly reduce computational costs, and substantially accelerate query execution.
The query rewrite feature based on asynchronous materialized views is particularly useful in the following scenarios:
Pre-aggregation of metricsYou can use materialized views to create a pre-aggregated metric layer if you are dealing with a high dimensionality of data.
Joins of wide tablesMaterialized views allow you to transparently accelerate queries with joins of multiple large wide tables in complex scenarios.
Query acceleration in the data lakeBuilding an external catalog-based materialized view can easily accelerate queries against data in your data lake.
NOTE
Asynchronous materialized views created on base tables in a JDBC catalog do not support query rewrite.
StarRocks' asynchronous materialized view-based automatic query rewrite features the following attributes:
Strong data consistency: If the base tables are native tables, StarRocks ensures that the results obtained through the materialized view-based query rewrite are consistent with the results returned from the direct query against the base tables.
Staleness rewrite: StarRocks supports staleness rewrite, allowing you to tolerate a certain level of data expiration to cope with scenarios with frequent data changes.
Multi-table joins: StarRocks' asynchronous materialized view supports various types of joins, including some complex join scenarios like View Delta Joins and Derivable Joins, allowing you to accelerate queries in scenarios involving large wide tables.
Aggregation rewrite: StarRocks can rewrite queries with aggregations to improve report performance.
Nested materialized view: StarRocks supports rewriting complex queries based on nested materialized views, expanding the scope of queries that can be rewritten.
Union rewrite: You can combine the Union rewrite feature with the TTL (Time-to-Live) of the materialized view's partitions to achieve the separation of the hot and cold data, which allows you to query hot data from materialized views and historical data from the base table.
Materialized views on views: You can accelerate the queries in scenarios with data modeling based on views.
Materialized views on external catalogs: You can accelerate queries in data lakes.
Complex expression rewrite: It can handle complex expressions, including function calls and arithmetic operations, catering to advanced analytical and calculation requirements.
These features will be elaborated in the following sections.
StarRocks supports rewriting queries with various types of joins, including Inner Join, Cross Join, Left Outer Join, Full Outer Join, Right Outer Join, Semi Join, and Anti Join.
The following is an example of rewriting queries with joins. Create two base tables as follows:
With the above base tables, you can create a materialized view as follows:
CREATE MATERIALIZED VIEW join_mv1 DISTRIBUTEDBYHASH(lo_orderkey) AS SELECT lo_orderkey, lo_linenumber, lo_revenue, lo_partkey, c_name, c_address FROM lineorder INNERJOIN customer ON lo_custkey = c_custkey;
Such a materialized view can rewrite the following query:
SELECT lo_orderkey, lo_linenumber, lo_revenue, c_name, c_address FROM lineorder INNERJOIN customer ON lo_custkey = c_custkey;
StarRocks supports rewriting join queries with complex expressions, such as arithmetic operations, string functions, date functions, CASE WHEN expressions, and OR predicates. For example, the above materialized view can rewrite the following query:
SELECT lo_orderkey, lo_linenumber, (2* lo_revenue +1)* lo_linenumber, upper(c_name), substr(c_address,3) FROM lineorder INNERJOIN customer ON lo_custkey = c_custkey;
In addition to the conventional scenario, StarRocks further supports rewriting join queries in more complicated scenarios.
Query Delta Join refers to a scenario in which the tables joined in a query are a superset of the tables joined in a materialized view. For instance, consider the following query that involves joins of three tables: lineorder, customer, and part. If the materialized view join_mv1 contains only the join of lineorder and customer, StarRocks can rewrite the query using join_mv1.
Example:
SELECT lo_orderkey, lo_linenumber, lo_revenue, c_name, c_address, p_name FROM lineorder INNERJOIN customer ON lo_custkey = c_custkey INNERJOIN part ON lo_partkey = p_partkey;
Its original query plan and the one after the rewrite are as follows:
View Delta Join refers to a scenario in which the tables joined in a query are a subset of the tables joined in a materialized view. This feature is typically used in scenarios involving large wide tables. For example, in the context of the Star Schema Benchmark (SSB), you can create a materialized view that joins all tables to improve query performance. Through testing, it has been found that query performance for multi-table joins can achieve the same level of performance as querying the corresponding large wide table after transparently rewriting the queries through the materialized view.
To perform a View Delta Join rewrite, the materialized view must contain the 1:1 cardinality preservation join that does not exist in the query. Here are the nine types of joins that are considered cardinality preservation joins, and satisfying any one of them enables View Delta Join rewriting:
Take SSB tests as an example, create the following base tables:
Create the materialized view lineorder_flat_mv that joins lineorder, customer, supplier, part, and dates:
CREATE MATERIALIZED VIEW lineorder_flat_mv DISTRIBUTEDBYHASH(LO_ORDERDATE, LO_ORDERKEY) BUCKETS 48 PARTITIONBY LO_ORDERDATE REFRESH MANUAL PROPERTIES ( "session.partition_refresh_nubmer"="1" ) ASSELECT/*+ SET_VAR(query_timeout = 7200) */-- Set timeout for the refresh operation. l.LO_ORDERDATE AS LO_ORDERDATE, l.LO_ORDERKEY AS LO_ORDERKEY, l.LO_LINENUMBER AS LO_LINENUMBER, l.LO_CUSTKEY AS LO_CUSTKEY, l.LO_PARTKEY AS LO_PARTKEY, l.LO_SUPPKEY AS LO_SUPPKEY, l.LO_ORDERPRIORITY AS LO_ORDERPRIORITY, l.LO_SHIPPRIORITY AS LO_SHIPPRIORITY, l.LO_QUANTITY AS LO_QUANTITY, l.LO_EXTENDEDPRICE AS LO_EXTENDEDPRICE, l.LO_ORDTOTALPRICE AS LO_ORDTOTALPRICE, l.LO_DISCOUNT AS LO_DISCOUNT, l.LO_REVENUE AS LO_REVENUE, l.LO_SUPPLYCOST AS LO_SUPPLYCOST, l.LO_TAX AS LO_TAX, l.LO_COMMITDATE AS LO_COMMITDATE, l.LO_SHIPMODE AS LO_SHIPMODE, c.C_NAME AS C_NAME, c.C_ADDRESS AS C_ADDRESS, c.C_CITY AS C_CITY, c.C_NATION AS C_NATION, c.C_REGION AS C_REGION, c.C_PHONE AS C_PHONE, c.C_MKTSEGMENT AS C_MKTSEGMENT, s.S_NAME AS S_NAME, s.S_ADDRESS AS S_ADDRESS, s.S_CITY AS S_CITY, s.S_NATION AS S_NATION, s.S_REGION AS S_REGION, s.S_PHONE AS S_PHONE, p.P_NAME AS P_NAME, p.P_MFGR AS P_MFGR, p.P_CATEGORY AS P_CATEGORY, p.P_BRAND AS P_BRAND, p.P_COLOR AS P_COLOR, p.P_TYPE AS P_TYPE, p.P_SIZE AS P_SIZE, p.P_CONTAINER AS P_CONTAINER, d.D_DATE AS D_DATE, d.D_DAYOFWEEK AS D_DAYOFWEEK, d.D_MONTH AS D_MONTH, d.D_YEAR AS D_YEAR, d.D_YEARMONTHNUM AS D_YEARMONTHNUM, d.D_YEARMONTH AS D_YEARMONTH, d.D_DAYNUMINWEEK AS D_DAYNUMINWEEK, d.D_DAYNUMINMONTH AS D_DAYNUMINMONTH, d.D_DAYNUMINYEAR AS D_DAYNUMINYEAR, d.D_MONTHNUMINYEAR AS D_MONTHNUMINYEAR, d.D_WEEKNUMINYEAR AS D_WEEKNUMINYEAR, d.D_SELLINGSEASON AS D_SELLINGSEASON, d.D_LASTDAYINWEEKFL AS D_LASTDAYINWEEKFL, d.D_LASTDAYINMONTHFL AS D_LASTDAYINMONTHFL, d.D_HOLIDAYFL AS D_HOLIDAYFL, d.D_WEEKDAYFL AS D_WEEKDAYFL FROM lineorder AS l INNERJOIN customer AS c ON c.C_CUSTKEY = l.LO_CUSTKEY INNERJOIN supplier AS s ON s.S_SUPPKEY = l.LO_SUPPKEY INNERJOIN part AS p ON p.P_PARTKEY = l.LO_PARTKEY INNERJOIN dates AS d ON l.LO_ORDERDATE = d.D_DATEKEY;
SSB Q2.1 involves joining four tables, but it lacks the customer table compared to the materialized view lineorder_flat_mv. In lineorder_flat_mv, lineorder INNER JOIN customer is essentially a cardinality preservation join. Therefore, logically, this join can be eliminated without affecting the query results. As a result, Q2.1 can be rewritten using lineorder_flat_mv.
SSB Q2.1:
SELECTsum(lo_revenue)AS lo_revenue, d_year, p_brand FROM lineorder JOIN dates ON lo_orderdate = d_datekey JOIN part ON lo_partkey = p_partkey JOIN supplier ON lo_suppkey = s_suppkey WHERE p_category ='MFGR#12'AND s_region ='AMERICA' GROUPBY d_year, p_brand ORDERBY d_year, p_brand;
Its original query plan and the one after the rewrite are as follows:
Similarly, other queries in the SSB can also be transparently rewritten using lineorder_flat_mv, thus optimizing query performance.
Join Derivability refers to a scenario in which the join types in the materialized view and the query are not consistent, but the materialized view's join results contain the results of the query's join. Currently, it supports two scenarios - joining three or more tables, and joining two tables.
Scenario one: Joining three or more tablesSuppose the materialized view contains a Left Outer Join between tables t1 and t2 and an Inner Join between tables t2 and t3. In both joins, the join condition includes columns from t2.The query, on the other hand, contains an Inner Join between t1 and t2, and an Inner Join between t2 and t3. In both joins, the join condition includes columns from t2.In this case, the query can be rewritten using the materialized view. This is because in the materialized view, the Left Outer Join is executed first, followed by the Inner Join. The right table generated by the Left Outer Join has no results for the matching (that is, columns in the right table are NULL). These results are subsequently filtered out during the Inner Join. Therefore, the logic of the materialized view and the query is equivalent, and the query can be rewritten.Example:Create the materialized view join_mv5:
CREATE MATERIALIZED VIEW join_mv5 PARTITIONBY lo_orderdate DISTRIBUTEDBYhash(lo_orderkey) PROPERTIES ( "partition_refresh_number"="1" ) AS SELECT lo_orderkey, lo_orderdate, lo_linenumber, lo_revenue, c_custkey, c_address, p_name FROM customer LEFTOUTERJOIN lineorder ON c_custkey = lo_custkey INNERJOIN part ON p_partkey = lo_partkey;
join_mv5 can rewrite the following query:
SELECT lo_orderkey, lo_orderdate, lo_linenumber, lo_revenue, c_custkey, c_address, p_name FROM customer INNERJOIN lineorder ON c_custkey = lo_custkey INNERJOIN part ON p_partkey = lo_partkey;
Its original query plan and the one after the rewrite are as follows:
Similarly, if the materialized view is defined as t1 INNER JOIN t2 INNER JOIN t3, and the query is LEFT OUTER JOIN t2 INNER JOIN t3, the query can also be rewritten. Furthermore, this rewriting capability extends to scenarios involving more than three tables.
Scenario two: Joining two tablesThe Join Derivability Rewrite feature involving two tables supports the following specific cases:
In cases 1 to 9, filtering predicates must be added to the rewritten result to ensure semantic equivalence. For example, create a materialized view as follows:
CREATE MATERIALIZED VIEW join_mv3 DISTRIBUTEDBYhash(lo_orderkey) AS SELECT lo_orderkey, lo_linenumber, lo_revenue, c_custkey, c_address FROM lineorder LEFTOUTERJOIN customer ON lo_custkey = c_custkey;
The following query can be rewritten using join_mv3, and the predicate c_custkey IS NOT NULL is added to the rewritten result:
SELECT lo_orderkey, lo_linenumber, lo_revenue, c_custkey, c_address FROM lineorder INNERJOIN customer ON lo_custkey = c_custkey;
Its original query plan and the one after the rewrite are as follows:
In case 10, the Left Outer Join query must include the filtering predicate IS NOT NULL in the right table, for example, =, <>, >, <, <=, >=, LIKE, IN, NOT LIKE, or NOT IN. For example, create a materialized view as follows:
CREATE MATERIALIZED VIEW join_mv4 DISTRIBUTEDBYhash(lo_orderkey) AS SELECT lo_orderkey, lo_linenumber, lo_revenue, c_custkey, c_address FROM lineorder INNERJOIN customer ON lo_custkey = c_custkey;
join_mv4 can rewrite the following query, where customer.c_address = "Sb4gxKs7" is the filtering predicate IS NOT NULL:
SELECT lo_orderkey, lo_linenumber, lo_revenue, c_custkey, c_address FROM lineorder LEFTOUTERJOIN customer ON lo_custkey = c_custkey WHERE customer.c_address ="Sb4gxKs7";
Its original query plan and the one after the rewrite are as follows:
StarRocks' asynchronous materialized view supports rewriting multi-table aggregate queries with all available aggregate functions, including bitmap_union, hll_union, and percentile_union. For example, create a materialized view as follows:
CREATE MATERIALIZED VIEW agg_mv1 DISTRIBUTEDBYhash(lo_orderkey) AS SELECT lo_orderkey, lo_linenumber, c_name, sum(lo_revenue)AS total_revenue, max(lo_discount)AS max_discount FROM lineorder INNERJOIN customer ON lo_custkey = c_custkey GROUPBY lo_orderkey, lo_linenumber, c_name;
It can rewrite the following query:
SELECT lo_orderkey, lo_linenumber, c_name, sum(lo_revenue)AS total_revenue, max(lo_discount)AS max_discount FROM lineorder INNERJOIN customer ON lo_custkey = c_custkey GROUPBY lo_orderkey, lo_linenumber, c_name;
Its original query plan and the one after the rewrite are as follows:
The following sections expound on the scenarios where the Aggregation Rewrite feature can be useful.
StarRocks supports rewriting queries with Aggregation Rollup, that is, StarRocks can rewrite aggregate queries with a GROUP BY a clause using an asynchronous materialized view created with a GROUP BY a,b clause. For example, the following query can be rewritten using agg_mv1:
SELECT lo_orderkey, c_name, sum(lo_revenue)AS total_revenue, max(lo_discount)AS max_discount FROM lineorder INNERJOIN customer ON lo_custkey = c_custkey GROUPBY lo_orderkey, c_name;
Its original query plan and the one after the rewrite are as follows:
NOTE
Currently, rewriting the grouping set, grouping set with rollup, or grouping set with cube is not supported.
Only certain aggregate functions support query rewrite with Aggregate Rollup. In the preceding example, if the materialized view order_agg_mv uses count(distinct client_id) instead of bitmap_union(to_bitmap(client_id)), StarRocks cannot rewrite the queries with Aggregate Rollup.
The following table shows the correspondence between the aggregate functions in the original query and the aggregate function used to build the materialized view. You can select the corresponding aggregate functions to build a materialized view according to your business scenario.
Aggregate function suppprted in original queries
Function supported Aggregate Rollup in materialized view
DISTINCT aggregates without the corresponding GROUP BY column cannot be rewritten with Aggregate Rollup. However, from StarRocks v3.1 onwards, if a query with an Aggregate Rollup DISTINCT aggregate function does not have a GROUP BY column but an equal predicate, it can also be rewritten by the relevant materialized view because StarRocks can convert the equal predicates into a GROUP BY constant expression.
In the following example, StarRocks can rewrite the query with the materialized view order_agg_mv1.
CREATE MATERIALIZED VIEW order_agg_mv1 DISTRIBUTEDBYHASH(`order_id`) BUCKETS 12 REFRESH ASYNC START('2022-09-01 10:00:00') EVERY (interval1day) AS SELECT order_date, count(distinct client_id) FROM order_list GROUPBY order_date; -- Query SELECT order_date, count(distinct client_id) FROM order_list WHERE order_date='2023-07-03';
StarRocks supports rewriting COUNT DISTINCT calculations into bitmap-based calculations, enabling high-performance, precise deduplication using materialized views. For example, create a materialized view as follows:
CREATE MATERIALIZED VIEW distinct_mv DISTRIBUTEDBYhash(lo_orderkey) AS SELECT lo_orderkey, bitmap_union(to_bitmap(lo_custkey))AS distinct_customer FROM lineorder GROUPBY lo_orderkey;
It can rewrite the following query:
SELECT lo_orderkey,count(distinct lo_custkey) FROM lineorder GROUPBY lo_orderkey;
StarRocks supports rewriting queries using nested materialized view. For example, create the materialized views join_mv2, agg_mv2, and agg_mv3 as follows:
CREATE MATERIALIZED VIEW join_mv2 DISTRIBUTEDBYhash(lo_orderkey) AS SELECT lo_orderkey, lo_linenumber, lo_revenue, c_name, c_address FROM lineorder INNERJOIN customer ON lo_custkey = c_custkey; CREATE MATERIALIZED VIEW agg_mv2 DISTRIBUTEDBYhash(lo_orderkey) AS SELECT lo_orderkey, lo_linenumber, c_name, sum(lo_revenue)AS total_revenue, max(lo_discount)AS max_discount FROM join_mv2 GROUPBY lo_orderkey, lo_linenumber, c_name; CREATE MATERIALIZED VIEW agg_mv3 DISTRIBUTEDBYhash(lo_orderkey) AS SELECT lo_orderkey, sum(total_revenue)AS total_revenue, max(max_discount)AS max_discount FROM agg_mv2 GROUPBY lo_orderkey;
Their relationship is as follows:
agg_mv3 can rewrite the following query:
SELECT lo_orderkey, sum(lo_revenue)AS total_revenue, max(lo_discount)AS max_discount FROM lineorder INNERJOIN customer ON lo_custkey = c_custkey GROUPBY lo_orderkey;
Its original query plan and the one after the rewrite are as follows:
When the predicate scope of a materialized view is a subset of the predicate scope of a query, the query can be rewritten using a UNION operation.
For example, create a materialized view as follows:
CREATE MATERIALIZED VIEW agg_mv4 DISTRIBUTEDBYhash(lo_orderkey) AS SELECT lo_orderkey, sum(lo_revenue)AS total_revenue, max(lo_discount)AS max_discount FROM lineorder WHERE lo_orderkey <300000000 GROUPBY lo_orderkey;
It can rewrite the following query:
select lo_orderkey, sum(lo_revenue)AS total_revenue, max(lo_discount)AS max_discount FROM lineorder GROUPBY lo_orderkey;
Its original query plan and the one after the rewrite are as follows:
In this context, agg_mv5 contains data where lo_orderkey < 300000000. Data where lo_orderkey >= 300000000 is directly obtained from the base table lineorder. Finally, these two sets of data are combined using a UNION operation and then aggregated to obtain the final result.
Suppose you created a partitioned materialized view based on a partitioned table. When the partition range that a rewritable query scanned is a superset of the most recent partition range of the materialized view, the query will be rewritten using a UNION operation.
For example, consider the following materialized view agg_mv4. Its base table lineorder currently contains partitions from p1 to p7, and the materialized view also contains partitions from p1 to p7.
If a new partition p8, with a partition range of [("19990101"), ("20000101")), is added to lineorder, the following query can be rewritten using a UNION operation:
Its original query plan and the one after the rewrite are as follows:
As shown above, agg_mv5 contains the data from partitions p1 to p7, and the data from partition p8 is directly queried from lineorder. Finally, these two sets of data are combined using a UNION operation.
StarRocks supports creating materialized views based on views. Subsequent queries against the views can be transparently rewritten.
For example, create the following views:
CREATEVIEW customer_view1 AS SELECT c_custkey, c_name, c_address FROM customer; CREATEVIEW lineorder_view1 AS SELECT lo_orderkey, lo_linenumber, lo_custkey, lo_revenue FROM lineorder;
Then, create the following materialized view based on the views:
CREATE MATERIALIZED VIEW join_mv1 DISTRIBUTEDBYhash(lo_orderkey) AS SELECT lo_orderkey, lo_linenumber, lo_revenue, c_name FROM lineorder_view1 INNERJOIN customer_view1 ON lo_custkey = c_custkey;
During query rewrite, queries against customer_view1 and lineorder_view1 are automatically expanded to the base tables and then transparently matched and rewritten.
StarRocks supports building asynchronous materialized views on Hive catalogs, Hudi catalogs, and Iceberg catalogs, and transparently rewriting queries with them. External catalog-based materialized views support most of the query rewrite capabilities, but there are some limitations:
Hudi, Iceberg, or JDBC catalog-based materialized views do not support Union rewrite.
Hudi, Iceberg, or JDBC catalog-based materialized views do not support View Delta Join rewrite.
Hudi, Iceberg, or JDBC catalog-based materialized views do not support the incremental refresh of partitions.
You can configure the asynchronous materialized view query rewrite through the following session variables:
Variable
Default
Description
enable_materialized_view_union_rewrite
true
Boolean value to control if to enable materialized view Union query rewrite.
enable_rule_based_materialized_view_rewrite
true
Boolean value to control if to enable rule-based materialized view query rewrite. This variable is mainly used in single-table query rewrite.
nested_mv_rewrite_max_level
3
The maximum levels of nested materialized views that can be used for query rewrite. Type: INT. Range: [1, +∞). The value of 1 indicates that materialized views created on other materialized views will not be used for query rewrite.
You can check if your query is rewritten by viewing its query plan using the EXPLAIN statement. If the field TABLE under the section OlapScanNode shows the name of the corresponding materialized view, it means that the query has been rewritten based on the materialized view.
mysql> EXPLAIN SELECT order_id, sum(goods.price) AS total FROM order_list INNER JOIN goods ON goods.item_id1 = order_list.item_id2 GROUP BY order_id; +------------------------------------+ | Explain String | +------------------------------------+ | PLAN FRAGMENT 0 | | OUTPUT EXPRS:1: order_id | 8: sum | | PARTITION: RANDOM | | | | RESULT SINK | | | | 1:Project | | | <slot 1> : 9: order_id | | | <slot 8> : 10: total | | | | | 0:OlapScanNode | | TABLE: order_mv | | PREAGGREGATION: ON | | partitions=1/1 | | rollup: order_mv | | tabletRatio=0/12 | | tabletList= | | cardinality=3 | | avgRowSize=4.0 | | numNodes=0 | +------------------------------------+ 20 rows in set (0.01 sec)
By default, StarRocks enables query rewrite for asynchronous materialized views created based on the default catalog. You can disable this feature by setting the session variable enable_materialized_view_rewrite to false.
For asynchronous materialized views created based on an external catalog, you can disable this feature by setting the materialized view property force_external_table_query_rewrite to false using ALTER MATERIALIZED VIEW.
In terms of materialized view-based query rewrite, StarRocks currently has the following limitations:
StarRocks does not support rewriting queries with non-deterministic functions, including rand, random, uuid, and sleep.
StarRocks does not support rewriting queries with window functions.
Materialized views defined with statements containing LIMIT, ORDER BY, UNION, EXCEPT, INTERSECT, MINUS, GROUPING SETS, WITH CUBE, or WITH ROLLUP cannot be used for query rewrite.
Strong consistency of query results is not guaranteed between base tables and materialized views built on external catalogs.
Asynchronous materialized views created on base tables in a JDBC catalog do not support query rewrite.