Kenneth Oh
09/01/2026, 5:04 PMRocky
09/01/2026, 5:05 PMARRAY<STRING> into a space-separated STRING (e.g., using array_join(col, ' ')).
* Create the Index: Create a GIN index on this string column with a parser (e.g., english or standard).
sql
-- Example for the Materialized View
CREATE MATERIALIZED VIEW array_token_mv
...
AS SELECT
id,
array_join(tags, ' ') as tags_string, -- Join tokens into a searchable string
tags
FROM iceberg_table;
-- Add the index to the MV (if supported by your version) or the underlying table
ALTER TABLE mv_underlying_table ADD INDEX idx_tags (tags_string) USING GIN("parser" = "english");
* Shared-data Requirement: If you are on v4.1+, you must use the built-in inverted index implementation for shared-data clusters. Earlier versions only supported CLucene-based GIN on shared-nothing clusters.
* Querying: Use the MATCH predicate instead of array_contains to trigger the index:
sql
SELECT * FROM array_token_mv WHERE tags_string MATCH 'token1';
2. Optimize the Data Cache (Shared-Data Specific)
In a shared-data architecture, your performance is bound by object storage latency unless the data is cached.
* Data Cache Warmup: Use the CACHE SELECT command to proactively load the MV's data into the local NVMe/SSD cache of your CN/BE nodes.
* Increase Cache Persistence: Set the table property datacache.partition_duration for the MV to a high value to prevent "hot" token data from being evicted.
* Check for Cache Hits: Monitor your query profile for CompressedBytesReadRemote. If this is high, your cache is too small or data is being evicted.
3. Bitmap Indexing for Low-Cardinality Tokens
If your "tokens" belong to a relatively small set of unique values (e.g., categories, status codes):
* Bitmap Index: Add a bitmap index to the array column or a pre-unnested column. While StarRocks supports bitmap indexes, they are most effective on scalar columns.
* Global Dictionary: For multi-billion rows, converting string tokens to integer IDs using a Global Dictionary and then using Bitmap functions (e.g., bitmap_union, bitmap_contains) can be orders of magnitude faster than string-based array searches.
4. Partitioning and Bucketing
* Partition Alignment: Ensure the MV's partitions are aligned with the Iceberg table's partitions (e.g., by event_date). This allows the MV to perform incremental refreshes and enables partition pruning, skipping billions of rows during the initial scan.
* Bucketing: Use a high-cardinality column for bucketing (e.g., user_id or request_id) to ensure data is evenly distributed across your cluster nodes, maximizing parallelism.
5. Efficient Querying Patterns
* Avoid Unnesting Early: Use array_contains(col, 'token') or any_match predicates. Avoid UNNEST in the WHERE clause or joining against unnested rows, as this creates a massive intermediate result set on billion-row tables.
* Predicate Pushdown: Ensure your token filters are pushed down to the storage layer. Check the Query Profile to see if PushDownPredicates includes your array filters.
Summary Recommendation
If you need sub-second performance on billions of rows:
1. MV Design: Materialize the array as a space-separated string.Rocky
09/01/2026, 5:05 PMCACHE SELECT.
4. Query: Query using MATCH rather than array_contains.
References
• sql-reference/sql-functions/array-functions/any_match.md
• sql-reference/sql-functions/array-functions/all_match.md
• sql-reference/data-types/semi_structured/Array.md
• sql-reference/sql-functions/array-functions/array_contains_seq.md
• sql-reference/sql-functions/array-functions/array_contains_all.mdKenneth Oh
09/02/2026, 12:10 AMRocky
09/02/2026, 12:10 AMCACHE SELECT command immediately after your MV refresh completes. This proactively pulls the new data into the local cache so the first user query hits the disk instead of S3.
sql
-- Warm up the entire MV
CACHE SELECT * FROM your_mv_name;
-- Or warm up only the most recent partitions to save cache space
CACHE SELECT * FROM your_mv_name WHERE event_date >= '2023-10-01';
You can automate this by submitting it as a task:
sql
SUBMIT TASK warmup_mv AS CACHE SELECT * FROM your_mv_name;
2. Check datacache.partition_duration
If your MV is partitioned, StarRocks uses the datacache.partition_duration property to decide which partitions are "hot" enough to stay in the cache. If the refreshed data falls outside this window, it won't be cached at all.
* Recommendation: Set this to a duration that covers your queryable window (e.g., 30d for 30 days).
sql
ALTER TABLE your_mv_name SET ("datacache.partition_duration" = "30d");
3. Warehouse/Node Mismatch
In shared-data clusters, if you have multiple warehouses or if the warehouse that refreshes the MV is different from the warehouse that queries it, the cache will be empty on the querying nodes.
* Solution: Enable Cache Sharing. This allows nodes to fetch cache blocks from other nodes over the network instead of going back to S3.
sql
-- Set these globally or in your session
SET GLOBAL enable_datacache_sharing = true;
ADMIN SET FRONTEND CONFIG ("enable_trace_historical_node" = "true");
4. Asynchronous Cache Population
By default, StarRocks populates the cache asynchronously during a query to avoid slowing down the initial response. This means a single query might only cache 10–20% of the data it reads, requiring 4 or 5 consecutive runs to reach a 100% hit rate.
* Solution: If you want the very first query to be fast and fully cached, set enable_datacache_async_populate_mode = false during your warmup query, or simply use CACHE SELECT which is synchronous by design.
5. Disk I/O Adaptation
If your BE nodes are under heavy I/O load (common during a massive MV refresh), the I/O Adapter might skip writing to the local cache to prioritize the query/write itself.
* Diagnostic: Check your Query Profile for CompressedBytesReadRemote. If it's high despite repeated queries, check if the cache disk is full or if enable_datacache_io_adapter is skipping writes due to high latency.
Summary Checklist
1. Run CACHE SELECT after every refresh.
2. Set datacache.partition_duration high enough.
3. Enable enable_datacache_sharing if using multiple warehouses/large clusters.
4. Verify Cache Status: Run SHOW BACKENDS or SHOW COMPUTE NODES and check the CacheCapacity and CacheUsedCapacity columns to ensure you haven't run out of local disk space.
References
• deployment/shared_data/feature-support-shared-data.md
• data_source/block_cache_warmup.mdRocky
09/02/2026, 12:10 AMKenneth Oh
09/02/2026, 12:14 AMRocky
09/02/2026, 12:15 AM"datacache.enable" = "true". This is the default, but it ensures that when the MV is refreshed (written), the data is simultaneously pushed to the local NVMe/SSD cache of the node performing the refresh.
* Proactive Warmup: Immediately after your MV refresh job finishes, run a Cache Warmup command. Since your table is not partitioned, you must warm up the whole table:
sql
CACHE SELECT * FROM your_mv_name;
Tip: You can automate this by adding a second step to your refresh pipeline or using StarRocks' SUBMIT TASK to run it on a schedule.
* Disable Eviction for Dim Tables: Since these are dimensional tables (even at billion-row scales), you want them to persist in cache. Set a very high datacache.partition_duration (e.g., 100 years) so the cache logic never considers the data "cold" based on time.
sql
ALTER TABLE your_mv_name SET ("datacache.partition_duration" = "1000 MONTH");
* Enable Cache Sharing: In a multi-node cluster, the data might be cached on Node A, but your query might land on Node B. Enable cache sharing to allow nodes to fetch data from each other's local disks rather than S3:
sql
SET GLOBAL enable_datacache_sharing = true;
2. Guaranteeing MV Query Rewrite
StarRocks is strict about data consistency. If the underlying Iceberg table changes (even a single row), StarRocks will, by default, stop using the MV for query rewrite to avoid returning stale data. To force the use of the MV:
* Relax Consistency Checks: Add these properties to your MV definition. LOOSE consistency tells the optimizer to use the MV even if the base table has newer data.
sql
ALTER MATERIALIZED VIEW your_mv_name SET PROPERTIES (
"query_rewrite_consistency" = "LOOSE",
"force_external_table_query_rewrite" = "true"
);
* Staleness Grace Period: Alternatively, if you want to allow rewrite only if the MV is "fresh enough" (e.g., refreshed within the last hour), use:
sql
"mv_rewrite_staleness_second" = "3600"
* Text-Based Rewrite (v3.3+): If your queries are complex or involve the ARRAY tokens discussed earlier, ensure text-based rewrite is active. This allows the optimizer to match the query's AST directly to the MV's definition.
sql
SET enable_materialized_view_text_match_rewrite = true;
3. Optimizer Configuration for Large Scales
Because you are dealing with multi-billion row tables, the optimizer might sometimes time out while trying to find a rewrite path.
* Increase Rewrite Timeout: If you see in EXPLAIN that rewrite was skipped, increase the time limit:
sql
SET optimizer_materialized_view_timelimit = 2000; -- default is 1000ms
* Verify with Explain: Always verify that your queries are actually hitting the MV by running:
sql
EXPLAIN SELECT ... -- Look for 'MaterializedView: [your_mv_name]' in the plan
Summary of Properties for your MV
sql
CREATE MATERIALIZED VIEW your_mv_name
DISTRIBUTED BY HASH(id) -- Use a high cardinality column
PROPERTIES (
"datacache.enable" = "true",
"datacache.partition_duration" = "1000 MONTH",
"query_rewrite_consistency" = "LOOSE",
"force_external_table_query_rewrite" = "true"
)
AS SELECT ... FROM iceberg_table;
ReferencesRocky
09/02/2026, 12:15 AMKenneth Oh
09/02/2026, 12:19 AMRocky
09/02/2026, 12:19 AMquery_rewrite_consistency and mv_rewrite_staleness_second determines whether the optimizer prioritizes data freshness or query performance (via the Materialized View).
The Interaction Logic
If a query is within the mv_rewrite_staleness_second window, it will NOT go to Iceberg; it will use the Materialized View, even if query_rewrite_consistency is set to checked (the default).
Think of these properties as two different ways to satisfy the "Consistency Check":
1. query_rewrite_consistency:
* checked (Default): The optimizer checks if the base table (Iceberg) has been updated since the last MV refresh. If the base table is newer, the MV is considered "stale" and the rewrite fails unless a staleness window is defined.
* `loose`: The optimizer skips all consistency checks. It will use the MV regardless of how old it is or how much the base table has changed.
2. mv_rewrite_staleness_second:
* This property acts as a grace period for the checked consistency level.
* If you set mv_rewrite_staleness_second = 3600, you are telling StarRocks: "For the next hour after a refresh, treat this MV as consistent even if the Iceberg table changes."
* Within the window: Rewrite is successful (Uses MV).
* Outside the window: StarRocks reverts to the strict checked logic. If Iceberg has changed, it will go to Iceberg (Cold Read).
Summary Table
query_rewrite_consistency | Within staleness_second window? | Iceberg Data Changed? | Resulting Action
checked | Yes | Yes/No | Uses MV (Override triggered by staleness)
checked | No | Yes | Goes to Iceberg (Cold Read)
checked | No | No | Uses MV (Up to date)
loose | N/A | Yes/No | Uses MV (Always)
Recommendation for Dimensional Tables
Since you want to avoid cold reads at all costs for your dimensional tables:
* Option A (Time-based): Set mv_rewrite_staleness_second to a value slightly longer than your refresh interval (e.g., if you refresh every hour, set it to 3600). This ensures that during the refresh cycle, queries always hit the MV.
* Option B (Safest): Set query_rewrite_consistency = 'loose'. This is common for dimensional data where slightly older labels/tokens are acceptable in exchange for guaranteed sub-second performance.
One final note: If you find the query still goes to Iceberg, check the session variable optimizer_materialized_view_timelimit. If the optimizer takes too long to find the rewrite path on your billion-row schema, it will give up and default to the base table. Increase it to 2000 or 3000 ms if necessary.
References
• sql-reference/sql-statements/materialized_view/CREATE_MATERIALIZED_VIEW.md
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.md