Kenneth Oh
08/06/2026, 8:53 AMRocky
08/06/2026, 8:53 AMINSERT INTO blackhole() (specifically INSERT INTO BLACKHOLE() SELECT ...) is an internal mechanism used for Block Cache Warmup.
What it does
* Warms up the Cache: It triggers a full query execution path—fetching data from remote storage (like S3, HDFS, or a Data Lake)—which causes that data to be loaded into the local Block Cache of the Backend (BE) nodes.
* Discards Results: As the name "blackhole" implies, the data is not actually written to any persistent storage or table. It is discarded immediately after being processed and cached.
Primary Use Case
This is the underlying implementation for the CACHE SELECT statement introduced in StarRocks v3.3. It is used to:
1. Accelerate First-Time Queries: Pre-load data into the cache so that subsequent user queries are sub-second instead of waiting for remote I/O.
2. Performance Testing (PoC): Assess StarRocks' compute performance without being bottlenecked by external storage latency.
3. Scheduled Warmup: Pre-warm data for critical daily tasks (e.g., loading a BI dashboard's data at 7 AM before users arrive at 8 AM).
Example
While usually managed via CACHE SELECT, the internal logic follows this pattern:
sql
INSERT INTO BLACKHOLE()
SELECT * FROM remote_catalog.db.large_table
WHERE dt = '2023-10-01';
Note: If you want to warm up the cache manually, it is recommended to use the official `CACHE SELECT` syntax rather than calling INSERT INTO BLACKHOLE() directly, as the former is the supported user-user-facing command.
References
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
• table_design/StarRocks_table_design.md
• sql-reference/System_variable.md
• sql-reference/information_schema/engines.md
• data_source/External_table.md
• faq/Others.md
• table_design/table_design.mdKenneth Oh
08/06/2026, 9:02 AMRocky
08/06/2026, 9:02 AMdb classifier to map all queries hitting that database to a high-priority resource group.
sql
CREATE RESOURCE GROUP high_priority_group
TO (db='important_db')
WITH (
'cpu_weight' = '10', -- Higher weight = more CPU share
'mem_limit' = '50%'
);
2. Prioritize using Query Hints
If you cannot move tables to a different database, you can use a SQL hint to manually route a specific query to a high-priority resource group at execution time. This is useful for BI dashboards or specific critical reports.
sql
SELECT /*+ SET_VAR(resource_group = 'high_priority_group') */ *
FROM critical_table
JOIN other_table ON ...;
3. Prioritize by User or Role
You can create a specific database user or role for applications that access the high-priority tables. Queries from that user/role will then automatically inherit the priority of their assigned resource group.
sql
CREATE RESOURCE GROUP app_group
TO (user='reporting_service_user')
WITH ('cpu_weight' = '20');
4. Use Short Query Optimization
For tables where you expect very fast, point-lookup style queries, StarRocks automatically prioritizes "Short Queries" based on cost estimation. Since v3.3.5, you can use exclusive_cpu_cores in a resource group to reserve dedicated hardware resources for these types of workloads, preventing them from being blocked by heavy scan-intensive queries.
Summary of Resource Group Parameters
When you define your priority group, use these properties to control the "strength" of the priority:
* cpu_weight: A soft limit. If the system is busy, groups with higher weights get a larger proportional share of CPU.
* exclusive_cpu_cores: A hard limit (introduced in v3.3.5). Reserves specific cores that only this group can use.
* concurrency_limit: Prevents a group from being overwhelmed by too many simultaneous queries.
References
• administration/management/resource_management/resource_group.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.md
• faq/resource_isolation_faq.md
• sql-reference/System_variable.md
• administration/management/resource_management/resource_management.md
management)
References
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.mdKenneth Oh
08/06/2026, 9:24 AMRocky
08/06/2026, 9:24 AMcpu_weight that guarantees a query will finish "immediately." In StarRocks, cpu_weight is a relative proportional share, not a priority ranking.
If you want a specific database to have the absolute highest priority, you have two main strategies:
1. The cpu_weight Strategy (Relative/Soft Isolation)
cpu_weight (formerly cpu_core_limit) determines what percentage of the total CPU a group gets when the system is under load.
* How it works: If Group A has a weight of 100 and the default_group has a weight of 1, Group A will receive ~99% of the CPU cycles if both are trying to run at full capacity.
* The "Immediate" Effect: To make a group feel "immediate," give it a weight significantly higher than the others (e.g., 10x or 100x the weight of your standard groups).
* Limitation: This is a "soft" limit. A query might still experience a tiny sub-millisecond delay while the scheduler finishes a current time-slice for another task.
2. The exclusive_cpu_cores Strategy (Hard Isolation)
If "immediate" means you want the query to never wait for other workloads (even for a millisecond), you should use exclusive_cpu_cores (available in StarRocks v3.3.5+).
* How it works: This reserves specific CPU cores solely for that resource group. These cores sit idle if no queries are running in that group, rather than being used by others.
* Why use this: Because the cores are "exclusive," a query entering this group doesn't have to compete for a scheduling slot with a heavy background scan; it has its own dedicated hardware lane.
* Example:
sql
CREATE RESOURCE GROUP immediate_group
TO (db='critical_db')
WITH (
"exclusive_cpu_cores" = "4", -- Adjust based on your BE core count
"mem_limit" = "20%"
);
Comparison
Feature | cpu_weight | exclusive_cpu_cores
Type | Soft (Proportional) | Hard (Reserved)
Idle Behavior | Others can use the CPU | Cores stay idle (unless borrowing is on)
Best for | Prioritizing BI users over batch loads | Real-time APIs or mission-critical apps
"Immediate" feel | Very high (relative to others) | Absolute (dedicated hardware)
Recommendation: For most "high priority" needs, a cpu_weight of 10 (compared to a default of 1) is usually sufficient. If you need guaranteed sub-second response times for a specific table regardless of what else is happening on the cluster, use exclusive_cpu_cores.
References
• administration/management/resource_management/resource_group.md
• faq/resource_isolation_faq.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.md
• sql-reference/System_variable.md
• administration/management/resource_management/resource_management.md
management)
References
• administration/management/resource_management/resource_group.md
• faq/resource_isolation_faq.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.md
References
• administration/management/resource_management/resource_group.md
• faq/resource_isolation_faq.mdKenneth Oh
08/06/2026, 9:26 AMRocky
08/06/2026, 9:26 AMdb classifier in StarRocks resource groups does not support the catalog.database (fully qualified) syntax. It expects only the database name itself.
Even though your database resides in default_catalog, the resource group manager validates the name against the list of databases it knows. When you include the catalog prefix, StarRocks looks for a database literally named "`default_catalog.starrocks`", which does not exist.
How to fix it
Change your query to include only the database name:
sql
-- Use just 'starrocks' if that is the name of your database
CREATE RESOURCE GROUP high_priority_contextual
TO (db='starrocks')
WITH (
'cpu_weight' = '10',
'mem_limit' = '20%'
);
Important Notes:
1. Check the Database Name: Ensure the database actually exists by running SHOW DATABASES;. If you are trying to prioritize the built-in system database, note that it is usually named _statistics or `information_schema`—there is no default database named starrocks unless you created one.
2. External Catalogs: If you are trying to prioritize a database from an external catalog (like Hive or Iceberg), you should still only provide the database name (e.g., db='sales_db').
3. Case Sensitivity: Database names in classifiers are generally case-sensitive depending on your StarRocks configuration.
4. Multiple Databases: You can specify multiple databases by separating them with commas: TO (db='db1,db2,db3').
References
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.md
• administration/management/resource_management/resource_group.md
• faq/resource_isolation_faq.md
• administration/management/resource_management/resource_management.md
References
• data_source/catalog/iceberg/iceberg_catalog.md
• sql-reference/sql-statements/Catalog/CREATE_EXTERNAL_CATALOG.md
• data_source/External_table.mdKenneth Oh
08/09/2026, 5:13 AMMV rewrite fail for curated_channel_enriched: stale partitions refreshType=FULL
mv status:
last_refresh_duration: 21.8
last_refresh_state: SUCCESS
query_rewrite_status: VALID
last_refresh_error_code: 0Rocky
08/09/2026, 5:14 AMSUCCESS and VALID, the default behavior of the optimizer is to perform a version check at query time. If the base table's version is newer than the version captured in the MV, the MV is considered "stale" and the rewrite fails to ensure you don't get "old" data.
Why this happens with refreshType=FULL
The refreshType=FULL in the error message indicates that the MV is configured to refresh the entire dataset at once (likely because it is not partitioned or was created without a PARTITION BY clause).
In this mode, any change to the base table—even a single row insertion—invalidates the entire MV for transparent rewrite until the next refresh completes. If your base table is being updated frequently (e.g., every few minutes), the MV will almost always be "stale" by the time you query it.
How to Fix It
1. Tolerate a "Staleness" Window (Recommended)
If you don't need absolute real-time consistency, you can tell StarRocks to allow rewrites as long as the MV was refreshed within a certain time window.
sql
ALTER MATERIALIZED VIEW curated_channel_enriched
SET ("mv_rewrite_staleness_second" = "3600"); -- Allow data up to 1 hour old
2. Relax Consistency Requirements
You can change the consistency level to loose. In this mode, StarRocks will prioritize using the MV for query speed even if the base table has newer data.
sql
ALTER MATERIALIZED VIEW curated_channel_enriched
SET ("query_rewrite_consistency" = "loose");
3. Use Partitioning (For Incremental Refresh)
If the table is very large, consider recreating it as a partitioned MV. This allows StarRocks to track staleness at the partition level. If only the "today" partition is stale, StarRocks can still rewrite queries hitting "yesterday's" data using the MV, while pulling only the stale portion from the base table (Partition-level Union Rewrite).
4. Check for External Table Metadata (If applicable)
If your base table is in an external catalog (like Hive or Iceberg), ensure StarRocks can see the latest metadata. You may need to enable metadata caching or check the force_external_table_query_rewrite session variable if you are using older versions of StarRocks.
Summary of Diagnostic Tools
To confirm the exact version mismatch, you can run:
sql
-- See detailed reasons for rewrite failure
TRACE REASON MV <your_query>;
-- Check the exact version recorded in the MV vs Base Table
SHOW MATERIALIZED VIEWS LIKE 'curated_channel_enriched'\G
References
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.md
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.md
• using_starrocks/async_mv/use_cases/data_lake_query_acceleration_with_materialized_views.md
• sql-reference/sql-statements/materialized_view/CREATE_MATERIALIZED_VIEW.md
References
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.md
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.mdKenneth Oh
08/09/2026, 5:15 AMquery_rewrite_consistency
how long will it reference the mvRocky
08/09/2026, 5:15 AMquery_rewrite_consistency, the "how long" depends entirely on the value you choose. Here is the breakdown:
1. LOOSE (The "Forever" option)
If you set query_rewrite_consistency = 'loose', StarRocks will reference the MV indefinitely, regardless of how much the base table data has changed.
* Duration: Until the next manual or scheduled refresh successfully completes.
* Behavior: The optimizer completely skips the version check. Even if the MV is days old and the base table has millions of new rows, the query will still rewrite to use the MV.
2. CHECKED (The Default / Strong Consistency)
This is the default mode. The MV is only referenced as long as it is perfectly in sync with the base table.
* Duration: Until the next data modification (Insert, Update, Delete, or Load) happens on any base table.
* Behavior: As soon as a base table's version increases, the MV is marked "stale" for rewrite purposes.
3. mv_rewrite_staleness_second (The "Timed" option)
This is often the better middle ground. You keep the consistency check but give it a "grace period."
* Duration: Last Refresh Time + N seconds.
* Example: If you set mv_rewrite_staleness_second = '3600', the query will reference the MV for 1 hour after the last successful refresh, even if the base table data changes during that hour.
* Behavior: Once the clock passes that 1-hour window since the last refresh, the MV is considered stale again until the next refresh.
Summary Table
Setting | How long it references the MV | Risk
checked | Only while data is 100% identical. | Frequent rewrite failures if data loads often.
loose | Forever (until next refresh). | High risk of returning very old/incorrect data.
staleness_second | For the specific duration you define. | Controlled risk; good for "near real-time" BI.
Recommendation: For your curated_channel_enriched MV, if you are seeing "stale partitions" errors, try setting mv_rewrite_staleness_second to a value like 300 (5 minutes) or 3600 (1 hour) instead of using LOOSE. This ensures the MV is used for a predictable window of time without letting the data get "forever" out of date.
References
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.md
• using_starrocks/async_mv/Materialized_view.md
• sql-reference/sql-statements/materialized_view/CREATE_MATERIALIZED_VIEW.md
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.md
References
• administration/management/FE_configuration.md
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.md
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.mdKenneth Oh
08/09/2026, 5:16 AMRocky
08/09/2026, 5:16 AMis_active status is false), StarRocks will not use it for query rewrite, even if query_rewrite_consistency is set to loose.
In this scenario, StarRocks will automatically fall back and query the underlying external tables directly.
Here is the detailed breakdown of how StarRocks handles these two distinct states:
1. Stale vs. Inactive (Invalid)
* Stale (Data Inconsistency): The MV is "healthy" structurally, but the data in the base tables has changed. query_rewrite_consistency = 'loose' tells the optimizer: "I don't care if the data is old, use the MV anyway."
* Inactive/Invalid (Structural Issue): The MV is marked is_active = false. This usually happens after a schema change on a base table (e.g., a column was dropped or renamed). When an MV is inactive, it is considered "broken" and the optimizer will ignore it entirely.
2. What happens during the Query?
When you run a query that could have been rewritten by that MV:
1. The optimizer checks if any MVs match the query.
2. It finds your MV but sees that it is Inactive/Invalid.
3. Because the MV is unusable, the optimizer abandons the rewrite attempt for this specific MV.
4. The query falls back to the original execution plan, which scans the base external tables (e.g., Hive, Iceberg, etc.).
How to check and fix
You can verify the status and the fallback behavior using these commands:
* Check Status:
sql
-- Look for the 'is_active' column
SHOW MATERIALIZED VIEWS LIKE 'curated_channel_enriched';
* Verify Fallback (Explain):
sql
-- If you see the external table name in the scan node instead of the MV name,
-- it has fallen back to the base tables.
EXPLAIN SELECT ...;
* Fix an Inactive MV:
If the MV is inactive due to a minor metadata sync issue, you can try to reactivate it:
sql
ALTER MATERIALIZED VIEW curated_channel_enriched ACTIVE;
Note: If the base table schema changed in a way that breaks the MV's SQL definition, the ACTIVE command will fail, and you must drop and recreate the MV.
Summary
query_rewrite_consistency = 'loose' only bypasses the version/time check. It does not bypass the health/activity check. If the MV is invalid, StarRocks protects you by querying the source tables so you still get a result, albeit more slowly.
References
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.md
• using_starrocks/async_mv/Materialized_view.md
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.md
• sql-reference/sql-statements/materialized_view/ALTER_MATERIALIZED_VIEW.md
References
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.md
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.md
• using_starrocks/async_mv/Materialized_view.md
• knowledge/usage/best-practice.md
References
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.mdRocky
08/09/2026, 5:16 AMKenneth Oh
08/09/2026, 8:15 PMRocky
08/09/2026, 8:16 PMexclusive_cpu_cores is a per-node setting, not a cluster-wide setting.
In StarRocks, a resource group definition is applied to every Backend (BE) or Compute Node (CN) in the cluster individually.
Why you got the error
You calculated $12\text{ nodes} \times 16\text{ vCPUs} \times 0.5 = 96$ cores. However, when you set exclusive_cpu_cores = 96, StarRocks tries to reserve 96 cores on every single node. Since your nodes only have 16 vCPUs each, this is physically impossible.
The error message exclusive_cpu_cores cannot exceed the minimum number of CPU cores available on the backends minus one [13] tells us two things:
1. The Per-Node Limit: The maximum allowed value for exclusive_cpu_cores is (min_be_cpu_cores - 1). StarRocks always requires at least one "Shared Core" to be left available for system tasks and other resource groups.
2. Detected Cores: The [13] in your error indicates that StarRocks has detected the minimum number of cores across your 12 nodes as 14. (Since $14 - 1 = 13$). Even though r8g.4xlarge has 16 vCPUs, StarRocks or the underlying OS may be reporting 14 usable cores, or one of your nodes might be slightly different.
How to fix it
To allocate half of your total cluster capacity (96 cores total across 12 nodes) to this resource group, you should set the value to 8 (or 7 if you want to stay within the 14-core limit detected by the system).
sql
-- This will reserve 8 cores ON EACH of your 12 nodes.
-- Total reservation = 12 * 8 = 96 cores cluster-wide.
ALTER RESOURCE GROUP your_group_name
SET ("exclusive_cpu_cores" = "8");
Key Differences to Remember:
* exclusive_cpu_cores (Hard Isolation): Reserves a specific number of cores on each node. These cores are "locked" for this group and cannot be used by others unless enable_resource_group_cpu_borrowing is true.
* cpu_weight (Soft Isolation): Defines the relative priority of a group. Unlike exclusive_cpu_cores, this is a weight (e.g., 100 vs 200), not a count.
* Cluster vs Node: Resource management settings in StarRocks are almost always node-local. If you want a cluster-wide limit, you divide your desired total by the number of nodes.
Recommendation: Run SHOW BACKENDS; and check the CPUCores column to see exactly how many cores StarRocks detects for each node. This will explain why the system is capping you at 13 instead of 15.
References
• administration/management/resource_management/resource_group.md
• faq/resource_isolation_faq.md
• administration/management/BE_configuration.md
• best_practices/audit_log_resource_group.md
References
• administration/management/resource_management/resource_group.md
• faq/resource_isolation_faq.md
• administration/management/BE_configuration.md
• best_practices/audit_log_resource_group.mdKenneth Oh
08/09/2026, 8:17 PMRocky
08/09/2026, 8:17 PMdefault_wg group in your Prometheus metrics.
In StarRocks, any query or activity that is not explicitly assigned to a custom resource group is automatically funneled into the default group named default_wg.
1. Key Metrics to Monitor
All resource group metrics in StarRocks include a name label. Use this label in your Grafana queries to differentiate between groups.
Activity Type | Metric Name (Example) | Label Filter
Non-RG Activity | resource_group_running_queries | {name="default_wg"}
Specific RG Activity | resource_group_running_queries | {name="your_group_name"}
All Custom RG Activity | resource_group_running_queries | {name!="default_wg"}
2. Common Grafana Query Examples
A. Compare CPU Usage (Cores)
To see how many CPU cores your custom groups are using versus the rest of the system:
* Custom Groups: sum(resource_group_inuse_cpu_cores{name!="default_wg"})
* Non-RG (Default): resource_group_inuse_cpu_cores{name="default_wg"}
B. Monitor Query Concurrency
To see if your custom resource groups are "crowding out" the default traffic:
* Custom Groups: sum(resource_group_running_queries{name!="default_wg"})
* Non-RG (Default): resource_group_running_queries{name="default_wg"}
C. Memory Usage (Bytes)
* Custom Groups: sum(resource_group_mem_inuse_bytes{name!="default_wg"})
* Non-RG (Default): resource_group_mem_inuse_bytes{name="default_wg"}
3. Recommended Grafana Setup
1. Variable: Create a Grafana variable named $resource_group using the query label_values(resource_group_total_queries, name). This allows you to filter your entire dashboard by specific groups.
2. Stacked Graph: Create a stacked area chart for resource_group_inuse_cpu_cores grouped by the name label. This visually shows the proportion of the cluster being consumed by each group versus the default_wg.
3. FE vs BE Metrics:
* Use FE metrics (starting with starrocks_fe_query_resource_group...) for high-level query counts and latency percentiles.
* Use BE metrics (starting with resource_group_...) for actual resource consumption like CPU and Memory.
Important Note on Metric Visibility
The query_resource_group metrics are lazy-initialized. If you just created a resource group and haven't run a query against it yet, that group's name will not appear in Prometheus/Grafana until the first query is processed.
References
• administration/management/resource_management/resource_group.md
• faq/resource_isolation_faq.md
• administration/management/monitoring/metrics.md
References
• administration/management/resource_management/resource_group.md
• administration/management/monitoring/metrics.md
• faq/resource_isolation_faq.mdKenneth Oh
08/09/2026, 10:23 PMRocky
08/09/2026, 10:23 PMcpu_weight (the default for shared groups) or exclusive_cpu_cores, you must explicitly set those existing values to 0 in the same ALTER statement to "deactivate" them.
The Solution
To switch to exclusive_cpu_percent, you should set the other three CPU parameters to 0 in your query:
sql
ALTER RESOURCE GROUP high_priority_contextual SET (
'cpu_weight' = '0',
'cpu_weight_percent' = '0',
'exclusive_cpu_cores' = '0',
'exclusive_cpu_percent' = '50'
);
Why this is required
StarRocks categorizes resource groups into two modes based on these parameters:
1. Shared Mode: Uses cpu_weight or cpu_weight_percent. These groups compete for "Shared Cores" based on their relative weight.
2. Exclusive Mode: Uses exclusive_cpu_cores or exclusive_cpu_percent. These groups reserve specific physical cores that others cannot use.
Since a group cannot be both "Shared" and "Exclusive" at the same time, any attempt to set an Exclusive parameter while a Shared parameter is still positive (or vice versa) results in the error you saw.
Important Notes:
* Version Note: Percentage-based controls (exclusive_cpu_percent and cpu_weight_percent) were introduced in StarRocks 4.1. If you are on an older version, you must use exclusive_cpu_cores.
* Total Limit: Just like with cores, the total exclusive_cpu_percent across all resource groups must leave at least one core's worth of percentage available for the system/default groups.
* Memory: Ensure you have also defined a mem_limit for the group, as it is a required parameter for resource groups to function properly.
References
• administration/management/resource_management/resource_group.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.md
• faq/resource_isolation_faq.md
• sql-reference/sql-statements/cluster-management/resource_group/ALTER_RESOURCE_GROUP.md
References
• administration/management/resource_management/resource_group.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.md
• faq/resource_isolation_faq.md
• sql-reference/sql-statements/cluster-management/resource_group/ALTER_RESOURCE_GROUP.md
• best_practices/audit_log_resource_group.md
References
• administration/management/resource_management/resource_group.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.md
• faq/resource_isolation_faq.mdKenneth Oh
08/09/2026, 10:27 PMRocky
08/09/2026, 10:27 PM'resource_group' = 'some_group' property, that setting only applies to the background refresh task.
For a user query that gets rewritten:
* Classifier Match: StarRocks assigns the query to a resource group at the very beginning of the execution based on Classifiers (e.g., the user, role, or source_ip of the person running the query).
* The Rewrite: The optimizer rewrites the query to use the MV during the planning phase. This does not change the resource group that was already assigned to the query.
* Result: The rewritten query will continue to run under the resource group of the user who initiated it (or default_wg if no classifier matches).
2. Background Refresh Tasks
The resource_group property in the MV definition is strictly for Asynchronous Refresh operations.
* If you set 'resource_group' = 'heavy_tasks', then the INSERT OVERWRITE job that updates the MV data will run in that group.
* If you do not set this property, the refresh task runs in the system-defined default_mv_wg group.
3. How to target rewritten queries specifically
If your goal is to ensure that queries hitting these MVs are isolated from other traffic, you have two main options:
* Database Classifier: If you store all your "fast" MVs in a specific database (e.g., mv_db), you can create a resource group with a classifier for that database:
sql
CREATE RESOURCE GROUP fast_queries
TO (db = 'mv_db') -- All queries against tables/MVs in this DB use this RG
WITH (...);
* Cost-based Classifier (v3.1.4+): Since rewritten queries often have a much lower estimated cost than the original Iceberg scan, you can use the plan_cpu_cost_range classifier to shunt low-cost queries into a specific group:
sql
CREATE RESOURCE GROUP small_queries
TO (plan_cpu_cost_range = '(0, 1000]')
WITH (...);
How to Verify
You can verify which resource group a rewritten query is actually using by running:
sql
EXPLAIN VERBOSE <your_query>;
Look for the ResourceGroup field in the output. It will show the group assigned by the classifier regardless of whether the plan shows a SCAN [your_mv_name].
References
• administration/management/resource_management/resource_group.md
• best_practices/audit_log_resource_group.md
• faq/resource_isolation_faq.md
References
• best_practices/audit_log_resource_group.md
• administration/management/resource_management/resource_group.md
• faq/resource_isolation_faq.mdKenneth Oh
08/10/2026, 3:05 AMRocky
08/10/2026, 3:06 AMsql
ALTER RESOURCE GROUP your_group_name SET (
'concurrency_limit' = '20' -- Adjust based on your cluster size/CPU
);
* Why this helps: Instead of 100 queries all hitting the I/O threads at once and fighting for disk/network bandwidth (causing the 23s wait), only 20 will run, and the other 80 will wait in an "orderly" queue at the FE level.
2. Enable Global Query Queuing
If you want to protect the entire cluster regardless of the resource group, you can set global thresholds:
sql
-- Set these as global variables
SET GLOBAL query_queue_concurrency_limit = 100;
SET GLOBAL query_queue_mem_used_pct_limit = 0.9; -- Queue if memory > 90%
3. Tuning the "Scan I/O Queue" (BE Level)
The specific wait time you see (IOTaskWaitTime in profiles) refers to the BE's internal scanner_thread_pool. If your hardware (e.g., high-performance SSDs or large 10Gbps+ network for Iceberg) can handle more concurrency, you can increase the pool size in `be.conf`:
* scanner_thread_pool_thread_num: (Default 48) The number of threads used for scanning data. Increase this if your CPU and I/O bandwidth have headroom during the 23s wait.
* max_hdfs_scanner_num: (Default 50, v3.2+) Specifically limits concurrent scanners for external tables like Iceberg. If your bursts are hitting Iceberg, this is likely the bottleneck.
Recommended Action Plan
1. Check FE vs BE Queuing: Check the Query Queue metrics in Grafana. If the FE queue is empty but IOTaskWaitTime is high, your BE is over-saturated.
2. Apply `concurrency_limit`: Set this on your most "bursty" resource group to a value that keeps IOTaskWaitTime under 1–2 seconds.
3. For Iceberg: If you are hitting the Iceberg tables mentioned earlier, check if you've reached the max_hdfs_scanner_num limit. Increasing this slightly (e.g., to 100) can help if your remote storage (S3/HDFS) is fast enough.
References
• administration/management/resource_management/resource_group.md
• administration/management/resource_management/query_queues.md
• administration/management/BE_configuration.md
• best_practices/query_tuning/query_profile_operator_metrics.md
References
• administration/management/BE_configuration.md
• administration/management/resource_management/query_queues.mdKenneth Oh
08/10/2026, 3:54 AMRocky
08/10/2026, 3:55 AMpip_scan_com. If a heavy batch job saturates that pool, your interactive queries will wait in the "Scan I/O Queue."
When you set exclusive_cpu_percent (or exclusive_cpu_cores), StarRocks creates the following dedicated pools for that specific Resource Group (RG):
* pip_scan_<rg_id>: Dedicated threads for internal table scans.
* pip_con_scan_<rg_id>: Dedicated threads for external table scans (like your Iceberg tables).
* pip_exec_<rg_id>: Dedicated threads for query execution logic.
Because these pools are reserved for your RG, your scan tasks never enter the same queue as queries from other resource groups.
Configuration
To "reserve" these resources and prevent competition, configure your group as follows:
sql
ALTER RESOURCE GROUP high_priority_contextual SET (
'cpu_weight' = '0',
'cpu_weight_percent' = '0',
'exclusive_cpu_cores' = '0',
'exclusive_cpu_percent' = '50' -- Reserves 50% of CPU & dedicated scan threads
);
Important Distinctions
1. Thread vs. Hardware Isolation: This reserves the threads that perform the scanning. It prevents "head-of-line blocking" where your query is stuck behind a long-running scan. However, it does not physically partition the disk's IOPS or the network's bandwidth—the underlying hardware is still shared.
2. Borrowing: By default, if your reserved threads are idle, other groups can "borrow" them to avoid wasting CPU. If you want strict isolation where others can never use your cores (even when idle), set this BE configuration:
sql
-- Run this to disable borrowing cluster-wide
UPDATE information_schema.be_configs SET VALUE = "false" WHERE NAME = "enable_resource_group_cpu_borrowing";
3. Verification: You can verify that your dedicated scan pools have been created by running:
sql
SELECT * FROM information_schema.be_threads WHERE name LIKE '%pip_scan%';
You should see threads named pip_scan_<your_rg_id> instead of just the common pip_scan_com.
References
• administration/management/resource_management/resource_group.md
• faq/resource_isolation_faq.md
• best_practices/audit_log_resource_group.md
References
• administration/management/resource_management/resource_group.md
• administration/management/monitoring/metrics.md
• best_practices/audit_log_resource_group.md
• administration/management/BE_configuration.mdKenneth Oh
08/10/2026, 4:47 AMRocky
08/10/2026, 4:48 AMdb='your_db') would not be enough to distinguish them. Instead, follow this strategy:
1. Identify Workloads via User or Role
The most robust way to separate these is to use different database users or roles for your ETL pipeline and your application.
* Application User: app_user
* ETL User: etl_user
2. Create the Resource Groups
You can then create two resource groups. Use exclusive_cpu_cores for the application to give it dedicated, reserved CPU and I/O threads.
For the Application (Dedicated Resources)
This group will have a "hard limit" reservation. These cores are removed from the shared pool and reserved only for the application.
sql
CREATE RESOURCE GROUP app_link_group
TO (user='app_user')
WITH (
'exclusive_cpu_percent' = '40', -- Reserves 40% of all BE cores exclusively
'mem_limit' = '30%',
'concurrency_limit' = '100'
);
For the ETL Workload (Shared/Background)
ETL queries usually benefit from cpu_weight, which allows them to use idle resources but yield to other groups when the system is busy.
sql
CREATE RESOURCE GROUP etl_group
TO (user='etl_user')
WITH (
'cpu_weight' = '10', -- Soft limit: weight-based sharing
'mem_limit' = '40%',
'concurrency_limit' = '10' -- Limit ETL concurrency to protect the app
);
3. Advanced Separation (If using the same User)
If you cannot change the database user, you can distinguish them using other classifier properties:
* Source IP: If your ETL runs on a specific server/subnet.
sql
TO (user='common_user', source_ip='10.0.1.0/24') -- Routes ETL traffic by IP
* Query Cost (v3.1.4+): If ETL queries are significantly "heavier" (e.g., scanning more data), you can route them based on the optimizer's estimated cost.
sql
TO (plan_cpu_cost_range = '(10000.0, 99999999.0]') -- Routes heavy queries to ETL group
4. Manual Session Routing (The "Override" Method)
If your application or ETL tool allows running a "startup SQL" or session command, you can bypass the classifier entirely:
sql
-- In the ETL job session
SET resource_group = 'etl_group';
SELECT * FROM your_mv ...
This command forces the current connection into the specified group regardless of what the classifiers say.
Summary of Resource Allocation
* Application Group: By using exclusive_cpu_percent, StarRocks creates dedicated pip_exec and pip_scan thread pools for this group. Even if the ETL group initiates massive scans on the same MV, the application's scan tasks will run in their own reserved threads and won't be queued behind the ETL tasks.
* ETL Group: Will compete in the shared pool (pip_scan_com) with any other non-exclusive workloads, ensuring they don't impact your "Exclusive" application cores.
References
• administration/management/resource_management/resource_group.md
• faq/resource_isolation_faq.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.md
References
• administration/management/resource_management/resource_group.md
• faq/resource_isolation_faq.md
• administration/management/monitoring/metrics.mdRocky
08/10/2026, 4:48 AMKenneth Oh
08/10/2026, 10:35 PMRocky
08/10/2026, 10:35 PMORDER BY clause in your table or MV definition).
* Prefix Indexing: StarRocks automatically builds a "Short Key Index" on the first ~3 columns (up to 36 bytes) of your sort key. If your query filters by the leading columns of the sort key, it can perform millisecond point-seeks across 4 billion rows.
* Zone Maps (Pruning): For columns not at the front of the sort key, StarRocks maintains Min/Max indexes for every 64KB page. If your data is sorted by (col_a, col_b, col_c), even a filter on col_c will be highly effective because the range of values in each page is minimized, allowing StarRocks to skip ("prune") the vast majority of data.
* Z-Order Sorting: While StarRocks' native sort key is linear (lexicographical), you can use Materialized Views to effectively create multiple "perspectives" of the data sorted differently to satisfy different column combinations.
3. The "Gold Standard" for Iceberg: Async MVs
If you are querying Iceberg directly and need <1s latency consistently, the most reliable path is creating an Asynchronous Materialized View (MV) on top of the Iceberg table.
* Pre-Aggregation/Sorting: The MV can pre-calculate sums/counts or simply re-sort the 4B rows from Iceberg into StarRocks' native format.
* Query Rewrite: Users continue querying the Iceberg table, but the optimizer transparently redirects the query to the MV.
* Native Indexes: Once the data is in an MV, you can use Bitmap Indexes or Bloom Filter Indexes on those "combinations of columns" that aren't in the primary sort key.
Recommended Configuration
If you are currently seeing high wait times, check these settings:
1. Check Cache Hit Rate: SELECT * FROM information_schema.be_metrics WHERE name LIKE '%cache_hit%';
2. Sort Key Length: Ensure your most frequent filter columns are in the ORDER BY clause.
3. Scan Range: If a query scans too many rows (e.g., billions of rows without filters), it will not be <1s regardless of architecture. Use EXPLAIN ANALYZE to see PushDownPredicates and RowsRead.
Summary of Performance Expectation
Method | Expected Latency (4B Rows) | Best For
Iceberg (No Cache) | 10s - 120s+ | Cold/Ad-hoc analysis
Iceberg + Data Cache | 1s - 5s | Frequent dashboard queries
StarRocks Native/MV + Sort Key | < 100ms - 800ms | High-concurrency apps / sub-second
References
• deployment/shared_data/feature-support-shared-data.md
• best_practices/table_clustering.md
• using_starrocks/async_mv/use_cases/data_lake_query_acceleration_with_materialized_views.mdRocky
08/10/2026, 10:35 PMKenneth Oh
08/10/2026, 10:41 PM(col_a, col_b, col_c), even a filter on col_c will be highly effective because the range of values in each page is minimized, allowing StarRocks to skip ("prune") the vast majority of data.
* Z-Order Sorting: While StarRocks' native sort key is linear (lexicographical), you can use Materialized Views to effectively create multiple "perspectives" of the data sorted differently to satisfy different column combinations.Rocky
08/10/2026, 10:44 PMuser_id, event_time, region, and category.
1. The Primary Perspective (Sorted by User & Time)
This MV is optimized for queries filtering by user_id or event_time. The ORDER BY clause defines the Sort Key, which creates the sparse prefix index and optimizes the 64KB page Zone Maps.
sql
CREATE MATERIALIZED VIEW mv_user_behavior
-- Partitioning by time allows for incremental refreshes and partition pruning
PARTITION BY date_trunc('day', event_time)
DISTRIBUTED BY HASH(user_id) BUCKETS 64
REFRESH ASYNC EVERY(INTERVAL 1 HOUR)
-- The Sort Key: Mandatory for <1s range/point lookups
ORDER BY (user_id, event_time)
AS
SELECT user_id, event_time, region, category, revenue
FROM iceberg_catalog.db.raw_events;
2. The Second Perspective (Handling Different Combinations)
If you frequently filter by region and category, the first MV will be slow because those columns are at the end of the sort key. You create a second perspective (another MV) sorted differently:
sql
CREATE MATERIALIZED VIEW mv_regional_analysis
PARTITION BY date_trunc('day', event_time)
DISTRIBUTED BY HASH(region) BUCKETS 64
REFRESH ASYNC EVERY(INTERVAL 1 HOUR)
-- New Sort Key for a different combination of columns
ORDER BY (region, category, event_time)
AS
SELECT region, category, event_time, user_id, revenue
FROM iceberg_catalog.db.raw_events;
_The StarRocks optimizer will automatically route your query to mv_regional_analysis if you filter by region, even if your SQL queries the base Iceberg table._
How Zone Maps and Pruning Work
At 4 billion rows, StarRocks divides data into 64KB pages.
* For Sort Key Columns: Because data is physically ordered, the Min/Max values for user_id in each page will be very tight (e.g., Page 1: IDs 1–100, Page 2: 101–200). A query for user_id = 150 skips almost all 4 billion rows instantly.
* For Non-Sort Columns: StarRocks still maintains Zone Maps for revenue or category. If your data has a natural correlation (e.g., all high_revenue events happen in certain time blocks), the Zone Maps will still prune significantly even if they aren't the primary sort key.
Adding Secondary Indexes for "Point" Combinations
For columns that are highly unique (high cardinality) but aren't in your sort key, add a Bitmap Index to the MV:
sql
-- Speeds up "category = 'electronics'" if it's not the first sort column
CREATE INDEX idx_category ON mv_user_behavior(category) USING BITMAP;
-- For extremely high-cardinality IDs, use Bloom Filter
ALTER MATERIALIZED VIEW mv_user_behavior SET ("bloom_filter_columns" = "session_id");
Summary of Best Practices for 4B Rows
1. Prefix Index: Put the most frequent filter column first in ORDER BY.
2. Multiple MVs: Don't try to make one MV do everything. Create 2-3 MVs for your 2-3 most common "column combinations."
3. Data Caching: Ensure enable_storage_cache = true so the MV data stays on the local SSDs of the StarRocks nodes, avoiding S3 latency.
4. Z-Order Note: While StarRocks supports Z-order for local table maintenance (OPTIMIZE), for MVs, the "Multiple Perspectives" (multiple sort keys) approach is more robust for sub-second production workloads.
References
• table_design/indexes/Bitmap_index.mdRocky
08/10/2026, 10:44 PMKenneth Oh
08/10/2026, 10:49 PMRocky
08/10/2026, 10:50 PMencode_sort_key)
StarRocks v3.x introduced encode_sort_key, which allows you to create a Composite Sort Key. This is the closest equivalent to a Z-order or Hilbert curve. You pack multiple columns into a single binary column and use that as your ORDER BY key.
This allows a single MV to provide decent pruning performance for multiple columns simultaneously, rather than favoring only the first column in a list.
Example:
sql
CREATE MATERIALIZED VIEW mv_multisort
REFRESH ASYNC EVERY (INTERVAL 1 HOUR)
-- Define a composite sort key
ORDER BY (composite_key)
AS
SELECT
*,
-- Encodes 4 columns into one binary sortable key
encode_sort_key(col1, col2, col3, col4) as composite_key
FROM iceberg_catalog.db.large_table;
* How it works: The encode_sort_key ensures that the interleaved values of all four columns are used to order the data. This provides "fair" pruning for all 4 columns, which is much more efficient for the "8-option" problem than a standard linear sort.
2. Secondary Indexes (Bitmap & Bloom Filter)
If your 8 sort options are primarily used for filtering before the final sort, you can use secondary indexes. In StarRocks, these are "pointers" and do not replicate the whole table.
* Bitmap Indexes: Use these for columns with low to medium cardinality (e.g., state, category, gender).
* Bloom Filter Indexes: Use these for high cardinality columns (e.g., user_id, order_id) where you do exact equality checks (WHERE id = '...').
* Inverted Indexes (v3.3+): Use these for text search or range queries on non-sorted columns.
Example:
sql
-- Add indexes to a single "Master MV" instead of creating new ones
CREATE INDEX idx_cat ON mv_master(category) USING BITMAP;
ALTER MATERIALIZED VIEW mv_master SET ("bloom_filter_columns" = "user_id, device_id");
3. Leverage "Zone Map" Pruning
Every column in StarRocks has an automatic Min/Max index at the 64KB page level. If your data has any natural correlation (e.g., order_id generally increases with event_time), the system will skip pages even if the column isn't in the sort key.
To maximize this:
* Sort by the highest cardinality column that appears in the most queries.
* The optimizer will use the page-level Zone Maps to prune the other 7 columns. On 4 billion rows, even a 50% pruning via Zone Maps combined with the Data Cache (SSD) often results in sub-second responses.
Summary Strategy
Requirement | Recommended Solution | Overhead
8 sort/filter combos | encode_sort_key on the top 4–5 cols | Low (1 binary col)
Point lookups on IDs | Bloom Filter Index | Very Low
Filtering on Categories | Bitmap Index | Very Low
Extreme Performance | 2 MVs (1 sorted by Time, 1 by encode_sort_key) | Medium
Recommendation: Start by creating one MV using encode_sort_key for your most frequent 4 columns and add Bitmap/Bloom Filter indexes for the others. This single MV will likely handle all 8 options under 1 second once the data is cached.
References
• table_design/indexes/Prefix_index_sort_key.md
• sql-reference/sql-functions/utility-functions/encode_sort_key.md
• best_practices/table_clustering.md
• table_design/indexes/Bitmap_index.mdRocky
08/10/2026, 10:50 PMRocky
08/10/2026, 10:50 PMRocky
08/10/2026, 10:50 PMKenneth Oh
08/11/2026, 4:42 AMRocky
08/11/2026, 4:43 AMORDER BY clause). This physically reorders the rows on disk to enable the high-performance "Prefix Index" and "Zone Map" pruning.
However, you can configure additional data structures on disk within that same MV to support multiple columns without creating 8 separate MVs. Here is how to achieve "multi-index" performance on a single MV:
1. Inverted Indexes (The "Alternative" Structure)
Starting from v3.3, StarRocks supports Inverted Indexes. This is a separate data structure stored on disk that maps column values to row IDs.
* Why it works: While it doesn't "sort" the table, it allows StarRocks to instantly find the rows matching your filter across any of your 8 columns. Once the rows are found via the index, the final sort becomes extremely fast because the data volume is already reduced.
* Support: It supports equality, range (>, <), and even MATCH (text search) queries.
sql
-- Adding an inverted index to an existing MV
CREATE INDEX idx_col_1 ON my_mv(col_1) USING INVERTED;
CREATE INDEX idx_col_2 ON my_mv(col_2) USING INVERTED;
2. The "Z-Order" Sort Key (Multi-Column Balance)
If you need multiple columns to have "equal weight" in pruning, do not use a standard ORDER BY (a, b, c, d). Instead, use encode_sort_key.
* Lexicographical Sort (Standard): ORDER BY (time, category, user) — Pruning is 100% efficient for time, but if you filter only by user, pruning is nearly 0%.
* Multi-Dimensional Sort: Using encode_sort_key(col1, col2, col3) creates a interleaved (Z-order-like) key. It provides "good" pruning for all three columns simultaneously, rather than "perfect" for one and "none" for others.
sql
CREATE MATERIALIZED VIEW mv_optimized
ORDER BY (composite_key)
AS SELECT
*,
encode_sort_key(col1, col2, col3, col4) as composite_key
FROM base_table;
3. Secondary Indexes (Bitmap & Bloom Filter)
These are lightweight structures that don't replicate the data but exist on disk to skip data blocks:
* Bitmap Index: Best for columns with <10,000 distinct values (e.g., city, status). It creates a bitmask on disk.
* Bloom Filter: Best for high-cardinality "ID" columns (e.g., order_id, user_id) to speed up equality checks.
Summary: The "1 MV" Configuration for 8 Options
Instead of 8 MVs, use one "Master MV" configured like this:
1. Primary Sort Key: Set ORDER BY to the 1 or 2 most common columns (e.g., event_time).
2. Inverted Indexes: Create Inverted Indexes on the other 6 columns to handle range and equality filters.
3. Bloom Filters: Enable Bloom Filters on high-cardinality IDs.
4. Data Cache: Ensure the MV's data and its indexes are cached in the CN local SSD (enable_storage_cache = true).
Result: When a query filters by any of the 8 columns, the Inverted Index or Bloom Filter identifies the relevant data pages in milliseconds. The CN nodes pull those specific pages from the local SSD cache, and the <1s requirement is met without the storage cost of 8 MVs.
References
• table_design/indexes/Inverted_index.md
• sql-reference/sql-functions/utility-functions/encode_sort_key.md
• sql-reference/sql-statements/materialized_view/CREATE_MATERIALIZED_VIEW.md
• table_design/indexes/Prefix_index_sort_key.md
References
• using_starrocks/caching/query_cache.md
• best_practices/table_clustering.mdRocky
08/11/2026, 4:43 AMKenneth Oh
08/11/2026, 4:46 AMsql
CREATE MATERIALIZED VIEW mv_optimized
ORDER BY (composite_key)
AS SELECT
*,
encode_sort_key(col1, col2, col3, col4) as composite_key
FROM base_table;
i can sort by col3 and not some special value being returned by encode_sort_key rightRocky
08/11/2026, 4:47 AMORDER BY col3 in your SQL queries, and you do not need to reference the "special value" (the binary blob) returned by encode_sort_key. StarRocks' optimizer will transparently use the Materialized View (MV) and its columns.
However, there is a critical distinction regarding performance and how encode_sort_key actually works:
1. encode_sort_key is NOT Z-Order
Despite the common misconception, encode_sort_key in StarRocks creates a Lexicographical (Linear) Sort, not a multi-dimensional Z-order.
* ORDER BY (encode_sort_key(col1, col2, col3)) is physically the same as ORDER BY (col1, col2, col3).
* Pruning Impact: If you filter by col3 alone, you will not get prefix-index pruning. The data is sorted by col1 first, then col2.
* Sorting Impact: If you query ORDER BY col3, StarRocks will still have to perform a full sort in memory/spill, because the data on disk is not globally ordered by col3.
2. Why encode_sort_key exists
This function is a technical helper used primarily when you want to use complex expressions or multiple columns as a sort key in versions or table types where the ORDER BY syntax was more restrictive. In modern StarRocks (v3.x+), you can simply define:
sql
CREATE MATERIALIZED VIEW mv_optimized
-- Native multi-column sort key
ORDER BY (col1, col2, col3, col4)
AS SELECT col1, col2, col3, col4, revenue FROM base_table;
This is preferred over encode_sort_key because it is more readable and the optimizer understands it better.
3. The Solution for "8 Sort Options"
If you have 8 different columns that users might sort or filter by, and you want to avoid 8 MVs, do not rely on a single Sort Key. Instead, use Inverted Indexes (introduced in v3.3).
Inverted Indexes are a separate data structure on disk that allows StarRocks to find matching rows for any of your 8 columns instantly, regardless of sort order.
Recommended Configuration for your MV:
1. Pick the "Master" Sort Key: Choose the 1 or 2 columns used in ~80% of queries (usually timestamp or tenant_id) and put them in the ORDER BY clause.
2. Add Inverted Indexes for the rest: For the other 6 columns, add an inverted index.
sql
CREATE MATERIALIZED VIEW mv_final
PARTITION BY ...
DISTRIBUTED BY ...
ORDER BY (primary_col) -- The most common filter/sort
AS SELECT col1, col2, col3, col4, col5, col6, col7, col8 FROM base_table;
-- Add inverted indexes to handle the other 7 "perspectives"
CREATE INDEX idx_col2 ON mv_final(col2) USING INVERTED;
CREATE INDEX idx_col3 ON mv_final(col3) USING INVERTED;
-- ... repeat for other columns
Summary of what happens at query time:
* Query: SELECT * FROM base_table WHERE col3 = 'XYZ' ORDER BY col3
* StarRocks Action:
1. Uses the Inverted Index on col3 to pinpoint the exact rows in the 4B-row table (skipping 99% of data).
2. Pulls only those specific blocks from disk/cache.
3. Performs a "Top-N" sort on the much smaller result set.
4. Result: Sub-second response without the storage overhead of 8 MVs.
Conclusion
Don't use encode_sort_key to solve the "8 options" problem; it won't provide the multidimensional pruning you expect. Instead, use a standard ORDER BY for your primary column and Inverted Indexes for the other seven.
References
• knowledge/usage/best-practice.md
• table_design/indexes/indexes.md
• table_design/indexes/vector_index.md
• table_design/indexes/Bitmap_index.mdKenneth Oh
08/11/2026, 4:54 AMRocky
08/11/2026, 4:55 AMORDER BY sort key does. Instead, it makes sorting faster by drastically reducing the input volume to the sorting process.
Here is exactly how it works under the hood for your 4 billion row table:
1. Indirect Speedup: Volume Reduction
If you run a query like SELECT * FROM table WHERE col3 = 'A' ORDER BY col3, the inverted index does the heavy lifting before the sort even begins:
* Without Index: StarRocks would have to scan 4 billion rows, filter them, and then send the result to the Sort operator.
* With Inverted Index: The index acts as a "map" that tells StarRocks the exact row IDs where col3 = 'A'. The engine then pulls only those specific data blocks from disk.
* Result: Sorting 1,000 rows (the filtered result) takes a few milliseconds, whereas sorting 4 billion rows takes minutes.
2. Top-N Optimization
When you use ORDER BY ... LIMIT N, StarRocks combines the inverted index with a Top-N sort.
1. The inverted index locates the rows matching your filter.
2. Instead of sorting everything, the system uses a Priority Queue (Heap) to keep track of only the top $N$ values as it reads the data blocks identified by the index.
3. This avoids a full "materialized" sort on disk, keeping the operation entirely in memory.
3. "Late Materialization"
StarRocks uses a technique called Late Materialization with its indexes.
* It first uses the inverted index to find the row IDs.
* It then evaluates the ORDER BY column for only those row IDs.
_ It only fetches the _other* requested columns (like a 100-column wide row) for the final result set after the sorting/filtering is done. This saves massive amounts of I/O.
Summary: Sort Key vs. Inverted Index
Feature | ***Sort Key (ORDER BY)*** | ***Inverted Index***
Data Order | Physically ordered on disk. | Unordered mapping (Value → Row ID).
Sort Speed | Zero sort needed (if it matches the prefix). | Physical sort still needed, but on much less data.
Filtering | Best for range queries on the first few columns. | Best for "needle in a haystack" filtering on any column.
Storage | None (it's just the table order). | Extra disk space (approx. 10-30% of column size).
The bottom line: For your 8 sort options, the inverted index makes the "filtering" phase so fast that the "sorting" phase becomes trivial. This is why you can achieve <1s response times without having 8 separate physical copies of the data.
References
• table_design/indexes/inverted_index.md
• table_design/indexes/indexes.md
• best_practices/table_clustering.md
• table_design/indexes/Prefix_index_sort_key.md
References
• faq/Sql_faq.md
• table_design/indexes/inverted_index.md
• table_design/indexes/Prefix_index_sort_key.md
• best_practices/table_clustering.mdKenneth Oh
08/11/2026, 5:03 AMRocky
08/11/2026, 5:03 AMSHOW DATA (Recommended)
This is the most reliable way to see the actual disk space occupied, including all replicas and indexes.
* For all tables/MVs in the current database:
sql
SHOW DATA;
* For a specific Materialized View:
sql
SHOW DATA FROM database_name.mv_name;
This will return Size, ReplicaCount, and RowCount.
Option 2: Querying information_schema
If you need to get the size of only Materialized Views across the entire cluster in a single result set, use this query:
sql
SELECT
TABLE_SCHEMA AS database_name,
TABLE_NAME AS mv_name,
DATA_LENGTH AS size_bytes,
format_bytes(DATA_LENGTH) AS human_readable_size,
TABLE_ROWS AS row_count
FROM information_schema.tables
WHERE TABLE_TYPE = 'VIEW' -- Note: ASYNC MVs are often listed as VIEW or BASE TABLE depending on version
OR TABLE_NAME IN (SELECT TABLE_NAME FROM information_schema.materialized_views);
Important Notes:
1. Index Space: The size reported by SHOW DATA includes the storage for the Inverted Indexes, Bitmap Indexes, and the primary Sort Key.
2. Replica Overhead: If you have a replication factor of 3, the total disk consumption will be roughly 3x the logical data size. SHOW DATA typically shows the sum of all replicas.
3. Accuracy: information_schema.tables values are based on background statistics and may have a slight delay. SHOW DATA is generally more real-time.
References
• faq/Sql_faq.md
• sql-reference/sql-statements/Database/SHOW_DATA.md
• sql-reference/information_schema/materialized_views.md
• administration/Monitor_and_Manage.md
• administration/management/resource_management/resource_group.md
• administration/management/resource_management/Resource_Group.md
• administration/management/resource_management/Workload_Group.md
• administration/management/resource_management/workload_group.md
• sql-reference/sql-statements/materialized_view/SHOW_MATERIALIZED_VIEW.md
References
• faq/Sql_faq.md
• sql-reference/information_schema/materialized_views.md
• sql-reference/sys/object_dependencies.md
• sql-reference/sql-statements/materialized_view/SHOW_MATERIALIZED_VIEW.md
• using_starrocks/async_mv/materialized_view_task_run_details.mdKenneth Oh
08/11/2026, 5:26 AMRocky
08/11/2026, 5:26 AMSHOW DATA command is a legacy diagnostic tool that does not support three-part naming (catalog.db.table). It only understands one or two levels: [db_name.]table_name.
Because Materialized Views and internal tables always reside in the default_catalog, you must ensure your session is pointed there first.
How to fix it
Option 1: Switch Catalog first (Recommended)
If your current session is pointing to an external catalog (like Hive or Iceberg), SHOW DATA will fail. Switch back to the internal catalog first:
sql
SET CATALOG default_catalog;
SHOW DATA FROM starrocks_db.your_mv_name;
Option 2: Use the standard SQL way
If you want to use three-part naming or query across catalogs without switching, use information_schema. This table tracks the same data and is much more flexible:
sql
SELECT
TABLE_SCHEMA,
TABLE_NAME,
format_bytes(DATA_LENGTH) as size,
TABLE_ROWS
FROM information_schema.tables
WHERE TABLE_SCHEMA = 'your_db_name'
AND TABLE_NAME = 'your_mv_name';
Why your queries failed:
1. SHOW DATA FROM default_catalog.starrocks: StarRocks interprets default_catalog as the database name and starrocks as the table name. If you don't have a database literally named "default_catalog", it returns an error.
2. SHOW DATA FROM default_catalog.starrocks.table_name: The parser for this specific command only expects two dots at most (for db.table). Adding the catalog name as a third part causes a syntax error.
Summary of SHOW DATA behavior:
* Context: It always looks inside the current catalog of your session.
* Database level: SHOW DATA; (Shows all tables in current DB).
* Table level: SHOW DATA FROM db_name.table_name; (Shows index/replica breakdown for one table).
References
• sql-reference/sql-statements/Database/SHOW_DATA.md
• sql-reference/sql-statements/table_bucket_part_index/SHOW_TABLES.md
• faq/Sql_faq.md
• data_source/catalog/unified_catalog.md
• sql-reference/sql-statements/Database/SHOW_DATABASES.md
• data_source/catalog/deltalake_catalog.md
• data_source/catalog/paimon_catalog.mdKenneth Oh
08/11/2026, 5:27 AMRocky
08/11/2026, 5:28 AMBIGINT, FLOAT, or DOUBLE) in StarRocks requires different strategies depending on the exact data type and whether you need to optimize for filtering or physical ordering.
1. For Integer Types (BIGINT, INT, SMALLINT)
If your columns are integers, you have two primary secondary index options. These do not physically sort the data but make the ORDER BY phase faster by reducing the data volume.
* Inverted Index (v3.3+): Best for high-cardinality numerical columns. By using an inverted index with 'parser'='none', StarRocks can instantly filter rows using >=, <=, IN, and ==. This "pruning" ensures the sorting engine only handles a fraction of the data.
* Bitmap Index: Best for low-to-medium cardinality columns. It supports BIGINT and INT and is highly efficient for combining multiple filters (e.g., WHERE age > 20 AND status = 1 ORDER BY salary).
2. For Floating Point Types (FLOAT, DOUBLE)
Important: StarRocks Bitmap Indexes do not support FLOAT or DOUBLE types. To speed up sorting on these columns, you should use one of the following:
* Materialized Views (The "Best" Solution): Since a table can only have one physical ORDER BY sort key, you can create a Synchronous Materialized View that uses your float column as its own sort key.
sql
-- Base table is sorted by id
CREATE MATERIALIZED VIEW mv_sort_by_price AS
SELECT price, id, user_id
FROM base_table
ORDER BY price; -- This creates a second physical copy sorted by price
StarRocks will automatically route your query to this MV when it sees ORDER BY price.
* Cast to DECIMAL: If you can afford the precision change, converting FLOAT to DECIMAL allows you to use Bitmap Indexes, which do support the DECIMAL type.
3. Comparison of Strategies
Strategy | Best For... | Supports Float/Double? | Storage Cost
Sort Key | Your #1 most frequent query. | No (only as last column) | 0 (Primary)
Inverted Index | Integer range filtering. | No (varies by version) | Low (~10-15%)
Materialized View | High-performance additional sorts. | Yes | High (100%)
Bloom Filter | Equality checks (=, IN). | Yes | Very Low
Summary Recommendation
* If you have 8 different sort options on a 4B row table, you cannot practically create 8 Materialized Views (it would consume 8x the disk space).
* Instead, use the Sort Key for the most common query.
* For the other 7 options, use Inverted Indexes on the integer columns and Bloom Filters on the float columns to filter the data as much as possible before the sort happens.
* If a specific "Sort by Price" (Float) query is still too slow, create one targeted Materialized View for just that specific high-priority column.
References
• knowledge/usage/best-practice.md
• table_design/indexes/Bitmap_index.md
• using_starrocks/distinct_values/Using_bitmap.mdKenneth Oh
08/11/2026, 5:32 AMRocky
08/11/2026, 5:33 AMUSING GIN) states it only supports string types, StarRocks provides a solution for numerical columns in shared-data 4.1 clusters through Bitmap Indexes.
Conceptually, a Bitmap Index is a type of inverted index, and in StarRocks v4.1 (shared-data), both the "built-in" inverted index and the bitmap index use the same underlying native implementation.
The Solution: Use Bitmap Index
You can create a Bitmap Index on numerical columns in a shared-data cluster. This feature was added to shared-data clusters starting in version 3.3.0.
Supported Numerical Types:
TINYINT, SMALLINT, INT, BIGINT, LARGEINT, DECIMAL, BOOLEAN.
How to create it:
sql
-- For an existing table
ALTER TABLE table_name ADD INDEX index_name (numerical_column) USING BITMAP;
-- At table creation
CREATE TABLE example (
id BIGINT,
data_value INT,
INDEX idx_val (data_value) USING BITMAP
) ENGINE=OLAP
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id);
Why use Bitmap instead of GIN?
1. Type Support: USING BITMAP is explicitly designed for scalar types, including integers and decimals. USING GIN is specifically for text analysis (tokenization) and currently enforces string types in the syntax parser.
2. Optimized Queries: Bitmap indexes are highly effective for numerical:
* Equality: WHERE col = 100
* Range: WHERE col > 10 AND col < 50
* In-list: WHERE col IN (1, 2, 3)
3. Shared-Data Compatibility: Since v3.3, StarRocks has supported Bitmap Indexes on shared-data (compute-decoupled) clusters.
Important Considerations for v4.1
* Built-in Implementation: In shared-data clusters, StarRocks automatically uses the "built-in" implementation for both Bitmap and GIN indexes because the older CLucene-based engine is not supported on shared-data.
* High Cardinality: Unlike some other systems, StarRocks Bitmap Indexes are optimized for high-cardinality columns as well.
* Float/Double: Note that FLOAT and DOUBLE are still not supported for Bitmap Indexes. If you need to index these, consider casting them to DECIMAL or using a Synchronous Materialized View to change the sort order.
References
• table_design/indexes/Bitmap_index.md
• table_design/indexes/inverted_index.md
• sql-reference/sql-statements/table_bucket_part_index/CREATE_INDEX.md
• release_notes/release-3.3.md
• table_design/indexes/indexes.md
• administration/management/resource_management/resource_group.md
• administration/management/resource_management/Resource_Group.md
• administration/management/resource_management/workload_group.md
• administration/management/resource_management/Workload_Group.md
load_Group)
References
• table_design/indexes/Bitmap_index.mdKenneth Oh
08/11/2026, 5:41 AMRocky
08/11/2026, 5:42 AMORDER BY)
The most effective way to support multi-column sorting is to define the physical order of the data.
* Duplicate Key Tables: Best for raw data/logs. You can specify multiple columns in the ORDER BY clause.
sql
CREATE TABLE events (
event_time DATETIME,
tenant_id INT,
severity INT,
message STRING
) ENGINE=OLAP
DUPLICATE KEY(event_time)
ORDER BY (tenant_id, event_time, severity); -- Physical multi-column sort
* Primary Key Tables (v3.0+): Unlike older versions, the Primary Key and Sort Key are now decoupled. This means you can have a UUID as the Primary Key but physically sort the data by timestamp and user_id.
sql
CREATE TABLE users (
user_id BIGINT,
city_id INT,
last_login DATETIME
) PRIMARY KEY (user_id)
ORDER BY (city_id, last_login);
2. Supporting Additional Sort Orders (Synchronous MVs)
If you have a table that needs to be sorted by (A, B) for one query and (C, D) for another, you cannot do this in a single physical file. In OpenSearch, this is slow because it's a "late materialization" sort. In StarRocks, you create a Synchronous Materialized View:
* It acts as a secondary "index" that is automatically kept in sync with the base table.
* It stores a second physical copy of the data sorted by the new columns.
* Query Rewrite: The optimizer automatically routes queries to the MV if the ORDER BY matches the MV's sort key.
sql
CREATE MATERIALIZED VIEW sort_by_severity AS
SELECT severity, event_time, tenant_id
FROM events
ORDER BY severity, event_time; -- This creates a 2nd physical sort order
3. "Doc Values" Equivalent for Numerical Columns: Bitmap Indexes
For numerical columns (INT, BIGINT, DECIMAL) that are not part of your sort key, use Bitmap Indexes.
* In StarRocks, Bitmap Indexes are highly efficient even for high-cardinality data.
* They allow the engine to "prune" rows before the sort happens. If you filter on 3 columns and sort by a 4th, the Bitmap Index reduces the amount of data the sorting engine has to process.
sql
ALTER TABLE events ADD INDEX idx_severity (severity) USING BITMAP;
4. Advanced: encode_sort_key (Composite Sorting)
If you have complex multi-column sorting requirements (e.g., sorting by a mix of types or handling specific null behaviors) that must be calculated on the fly, use the encode_sort_key() function.
* It creates a single VARBINARY string from multiple columns that preserves lexicographical order.
* You can store this result in a column or MV to perform extremely fast single-column sorts that represent multi-column logic.
5. Summary Strategy by Table Type
Table Type | Multi-Column Sort Strategy
Duplicate Key | Use ORDER BY (col1, col2...). Best for high-volume logs.
Primary Key | Decouple PK from Sort Key. Use ORDER BY for the most common query.
Aggregate/Unique | The sort key is implicitly the key columns. Use Synchronous MVs to re-order them for different query patterns.
Why this competes with OpenSearch:Rocky
08/11/2026, 5:42 AMKenneth Oh
08/11/2026, 5:45 AMRocky
08/11/2026, 5:46 AMSELECT * FROM table ORDER BY A, B, C) is extremely heavy. To match the instant response of "doc values," you should use Synchronous Materialized Views.
* Strategy: Create a Synchronous MV for each secondary sort option you need to support.
* Result: StarRocks stores a second physical copy of the data, sorted by those specific columns.
* Competitor Edge: Unlike OpenSearch, which reads doc values and sorts at query time (late materialization), StarRocks will read a pre-sorted stream of data. The sort operation becomes a simple sequential scan.
sql
-- Original table sorted by (id)
CREATE TABLE base_data ( ... )
ENGINE=OLAP DUPLICATE KEY(id)
ORDER BY (id);
-- Support "Sort by Metric A, Metric B" instantly
CREATE MATERIALIZED VIEW mv_sort_metrics AS
SELECT metric_a, metric_b, id, user_id
FROM base_data
ORDER BY metric_a, metric_b;
2. Handling Complex Multi-Column Logic (encode_sort_key)
If you need to sort by many columns (e.g., 5+ columns) or a mix of types, the overhead of the sorting engine increases. You can use a generated column with the encode_sort_key() function.
* This function collapses multiple columns (Long, Float, String, etc.) into a single VARBINARY string that preserves the exact lexicographical order of the original components.
* By making this the primary Sort Key, you reduce the 4-billion-row sort complexity from a multi-column comparison to a single-column binary comparison.
sql
CREATE TABLE high_scale_data (
col1 BIGINT,
col2 FLOAT,
composite_key VARBINARY(128) AS (encode_sort_key(col1, col2))
) ENGINE=OLAP
ORDER BY (composite_key);
3. Stability at Scale: "Spill to Disk" (v3.0.1+)
Sorting 4 billion rows without filtering will likely exceed the query_mem_limit of your BE nodes. In a shared-nothing cluster, StarRocks manages this via intermediate result spilling.
* How it works: When the sort operator hits memory limits, it spills chunks of the sorted data to local disk and performs a merge sort.
* Setup: Ensure you have high-performance local NVMe drives for the spill_local_storage_dir.
sql
SET enable_spill = true;
SET spill_mode = 'auto';
4. Optimize the Merge Phase
In a shared-nothing architecture, data is spread across many tablets.
* Local vs. Global Sort: Each BE node sorts its own tablets locally. The FE then oversees a global merge.
* Top-N Optimization: If you are sorting 4B rows but only need the top 1,000 (e.g., ORDER BY ... LIMIT 1000), StarRocks applies a Top-N Pushdown. Each BE node only sends its top 1,000 rows to the merger, reducing network traffic from 4 billion rows to just (BE_count * 1000) rows.
5. Why this beats "Doc Values" for 4B Rows
1. I/O Efficiency: OpenSearch "doc values" are columnar but require a random-access look-up or a heavy heap-sort on the values. StarRocks Sort Keys are part of the file format; the data is physically contiguous.
2. Vectorized Execution: StarRocks uses SIMD to compare multiple rows simultaneously during the merge-sort process.
3. Streaming Aggregate: If your sort is followed by an aggregation, StarRocks can use Sorted Streaming Aggregation (set enable_sort_aggregate=true;). It processes the 4B rows in their pre-sorted order and emits results without ever building a hash table, which is significantly faster and more memory-efficient than OpenSearch.
Summary Recommendation
For 4 billion rows with no filtering:
1. Primary Sort: Use the ORDER BY clause in your table definition.Rocky
08/11/2026, 5:46 AMencode_sort_key to flatten multi-column sorts into a single binary key.
References
• sql-reference/sql-functions/utility-functions/encode_sort_key.md
• best_practices/table_clustering.md
• administration/management/resource_management/spill_to_disk.md
• using_starrocks/sorted_aggregate.md
• faq/Sql_faq.md
References
• knowledge/usage/best-practice.md
• administration/management/resource_management/spill_to_disk.md
• using_starrocks/sorted_aggregate.md
• administration/management/resource_management/Memory_management.md
• faq/Sql_faq.mdKenneth Oh
08/11/2026, 5:59 AMRocky
08/11/2026, 5:59 AMUSING GIN)
Use this if: You are doing Full-Text Search or looking for a keyword within a sentence/log.
* Query Pattern: WHERE col MATCH 'token' or WHERE col MATCH_ANY 'token1 token2'.
* How it works: It breaks the string into individual words (tokenization). It stores a mapping of word -> row_id.
* Pros: Extremely fast for finding rows containing a specific word. Supports complex logic like "contains word A AND word B".
* Cons: Higher storage overhead than a Bloom filter.
2. Standard Bloom Filter
Use this if: Your "token" is a unique identifier (like a UUID or Session Token) and you are filtering for the exact, whole value.
* Query Pattern: WHERE col = 'exact_token_value' or WHERE col IN ('token1', 'token2').
* How it works: It hashes the entire column value into a bitset. It can quickly tell if a data block definitely does not contain that exact string.
* Pros: Very low storage overhead. Excellent for high-cardinality ID columns that are not part of the sort key.
* Cons: Cannot look "inside" a string. If the column value is "error_token_404" and you search for WHERE col = 'token', the Bloom filter will say it doesn't exist.
3. N-Gram Bloom Filter (Alternative)
Use this if: You are doing fuzzy matching or substring searches without full-text tokenization.
* Query Pattern: WHERE col LIKE '%token%'.
* How it works: It breaks the string into small chunks of $N$ characters (e.g., 3-character "grams") and puts them in a Bloom filter.
* Pros: Accelerates LIKE queries which usually require a full table scan.
──────────
Summary Recommendation
If your query is... | Use this Index | Syntax Example
"Find rows where the text contains the word..." | Inverted Index | INDEX idx (col) USING GIN
"Find rows where the ID is exactly..." | Bloom Filter | PROPERTIES("bloom_filter_columns"="col")
"Find rows where the string matches the pattern `%abc%`" | N-Gram Bloom Filter | INDEX idx (col) USING NGRAMBF
Note for StarRocks 4.1 (Shared-Data):
The Inverted Index is the most powerful option here. In shared-data clusters, the Inverted Index uses a native "built-in" format that is highly optimized for the compute-decoupled architecture. If you are unsure, the Inverted Index provides the most flexibility for string-performance-wise for string-based filtering.
References
• table_design/indexes/Ngram_Bloom_Filter_Index.md
• table_design/indexes/Bloomfilter_index.md
• knowledge/usage/best-practice.mdKenneth Oh
08/11/2026, 5:59 AMRocky
08/11/2026, 6:00 AMuser_id or order_id).
* The Problem: For a low-cardinality category like "Movies," every data block (tablet) will likely contain at least one row with that value.
* Result: The Bloom Filter will return "True" for almost every block, meaning StarRocks will still have to read every block from disk. It provides zero "pruning" power and just adds storage overhead.
2. The Best Choice: Native Dictionary Encoding (Default)
In StarRocks, low-cardinality string columns are automatically dictionary-encoded.
* Instead of storing the string "Movies" 4 billion times, it stores an integer (e.g., 5) and a small global dictionary.
* Performance: When you filter WHERE category = 'Movies', StarRocks converts the filter to WHERE category_id = 5 and scans the integer column, which is extremely fast and hardware-efficient.
* Recommendation: For 4 billion rows, if this column is used frequently in filters, try making it part of your Sort Key (ORDER BY) first. This allows "Zone Map" pruning, which is faster than any secondary index.
3. When to use a Bitmap Index
If the category column is not part of your Sort Key and you often filter by multiple categories at once (e.g., WHERE category = 'Movies' AND status = 'Released'), use a Bitmap Index.
* Why: Bitmap indexes are the traditional choice for low-cardinality data. They store a bitset for each unique value.
* Efficiency: StarRocks can perform bitwise AND/OR operations on these indexes before even touching the actual data, which is highly efficient for multi-column filtering on 4 billion rows.
4. When to use an Inverted Index (GIN)
In StarRocks 3.3+, the new Inverted Index can also handle exact matches, but it is typically optimized for text search (finding a word inside a sentence).
* If your "category" column is strictly single words like "Movies," "Action," or "Drama," an Inverted Index is technically functional but is usually "heavier" than a Bitmap Index.
* Use this ONLY if: You might occasionally want to do "contains" searches or if you are using a shared-data (Cloud Native) architecture where the modern Inverted Index is more tightly integrated into the storage layer.
Final Summary
Feature | Recommendation
Cardinality | Low (few unique values)
Best Strategy | No Index (Dictionary Encoding) + Sort Key (if possible)
Secondary Index | Bitmap Index (Great for combining multiple low-card filters)
Avoid | Bloom Filter (Will not prune data effectively at low cardinality)
For 4 Billion Rows: The absolute best performance will come from including the "Category" column as the first or second column in your ORDER BY clause. This will allow StarRocks to skip massive amounts of data at the storage level without needing to load index files.
References
• table_design/indexes/Bitmap_index.md
• table_design/indexes/Bloomfilter_index.md
• best_practices/query_tuning/schema_tuning.md
• knowledge/usage/best-practice.md
References
• table_design/indexes/Bitmap_index.mdKenneth Oh
08/11/2026, 6:04 AM0:OlapScanNode
TABLE: mv_2
PREAGGREGATION: ON
" PREDICATES: 149: category = 'Technology & Computing'"
partitions=1/1
rollup: mv_2
tabletRatio=2048/2048
tabletList=3402037,3402038,3402039,3402040,3402041,3402042,3402043,3402044,3402045,3402046 ...
cardinality=1684737
avgRowSize=211.50655
MaterializedView: trueRocky
08/11/2026, 6:04 AMtabletRatio=2048/2048 in your EXPLAIN plan are the "smoking guns."
tabletRatio=2048/2048 means your query is scanning 100% of the tablets in that Materialized View. Even though you have a predicate (category = '...'), StarRocks has to open and read every single tablet to find the relevant rows because it has no metadata to tell it where those rows are.
For 4 billion rows, you want that ratio to be as small as possible (e.g., 1/2048). Here is how to fix this:
1. The Best Fix: Add category to the Sort Key
In your Materialized View definition, the ORDER BY (Sort Key) determines which columns StarRocks uses for Zone Map pruning (skipping data based on Min/Max values).
* Problem: If category is not in the Sort Key (or is very late in the list), StarRocks must perform a full scan.
* Solution: Recreate the Materialized View with category as the first column in the ORDER BY clause.
sql
CREATE MATERIALIZED VIEW mv_2
REFRESH ASYNC
DISTRIBUTED BY HASH(...) -- keep your existing distribution
ORDER BY (category, ...) -- PUT CATEGORY FIRST
AS
SELECT category, ...
FROM base_table;
* Result: The tabletRatio will still show 2048/2048 (because it's not partitioned/distributed by category), but the ScanTime will drop from minutes to seconds because StarRocks will use "Zone Maps" to skip ~99% of the data blocks within those tablets.
2. The "Instant" Fix: Add a Bitmap Index
Since "Category" is low cardinality (as you mentioned earlier), a Bitmap Index is perfect. It allows StarRocks to skip reading data blocks entirely by checking a small index bitset first.
* Why it helps: It fixes the "2 minute" scan by providing a shortcut to the specific rows you need without requiring a full rewrite of the Sort Key.
sql
-- Run this on the Materialized View (or the base table if it's a sync MV)
ALTER TABLE mv_2 ADD INDEX idx_category (category) USING BITMAP;
3. Change Distribution (If category is your primary filter)
If 90% of your queries filter by category, you should consider distributing the data by that column.
* Current: DISTRIBUTED BY HASH(some_other_id) BUCKETS 2048 (Likely why ratio is 2048/2048).
* Change to: DISTRIBUTED BY HASH(category) BUCKETS 2048.
* Result: Your tabletRatio will change from 2048/2048 to 1/2048. The query will only talk to one tablet instead of 2,048.
_ _Warning:* Only do this if category has enough unique values to spread across your BE nodes. If you only have 5 categories, you'll get massive data skew.
Summary Checklist
1. Check `ORDER BY`: Is category the first column? If not, change it.
2. Add Bitmap Index: This is the easiest "bolt-on" performance boost for low-cardinality strings.
3. Check Tablet Count: 2,048 tablets is quite high unless you have a very large cluster (e.g., 50+ nodes). If your cluster is small, 2,048 tablets create high metadata overhead; consider reducing this to NodeCount * 10-20.
To see exactly where the time is going, run:
SET enable_profile = true;
Execute the query, then run SHOW PROFILE FOR <query_id>; and look for PushdownPredicates and RowsRead vs RawRowsRead. If RawRowsRead is 4 billion, you are doing a full table scan.
References
• using_starrocks/async_mv/materialized_view_task_run_details.md
• 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.mdKenneth Oh
08/11/2026, 6:10 AMRocky
08/11/2026, 6:10 AMmv_2) is stored physically as a table, use one of the following:
1. The Quickest Way: SHOW INDEX
This will list all manually created indexes (Bitmap, GIN, N-Gram, etc.).
sql
SHOW INDEX FROM mv_2;
2. View the Original Definition: SHOW CREATE TABLE
This is the most reliable way to see both the Sort Keys (which are "built-in" indexes) and any secondary indexes.
sql
SHOW CREATE TABLE mv_2;
* Look for ORDER BY: These columns are your "Prefix Index" and are used for Zone Map pruning.
* Look for INDEX: Any lines starting with INDEX or PROPERTIES("bloom_filter_columns"...) are your secondary indexes.
3. For Synchronous MVs: DESC ALL
If you are using a Synchronous (Rollup) MV, you must check it through the base table:
sql
DESC base_table_name ALL;
──────────
What to look for in your specific case:
In your previous EXPLAIN plan, the tabletRatio=2048/2048 confirms that you do not have an index that allows for "Partition Pruning" or "Distribution Pruning."
If SHOW INDEX returns empty for your category column:
1. Check if it's in the ORDER BY clause (via SHOW CREATE TABLE).
2. If it's not, you are suffering from a full scan.
3. To fix it immediately, add a Bitmap index:
sql
ALTER TABLE mv_2 ADD INDEX idx_category (category) USING BITMAP;
Note: If you just created the index, you may need to wait for a compaction or a refresh for it to fully apply to all 4 billion rows.
References
• table_design/indexes/Bitmap_index.md
• sql-reference/sql-statements/table_bucket_part_index/SHOW_INDEX.md
• using_starrocks/async_mv/Materialized_view.md
• table_design/indexes/Bloomfilter_index.md
• sql-reference/sql-statements/table_bucket_part_index/SHOW_CREATE_TABLE.md
References
• using_starrocks/Materialized_view-single_table.md
• using_starrocks/async_mv/Materialized_view.md
• using_starrocks/async_mv/troubleshooting_asynchronous_materialized_views.md
• sql-reference/information_schema/statistics.md
• table_design/indexes/indexes.mdKenneth Oh
08/11/2026, 6:26 AMRocky
08/11/2026, 6:26 AMCACHE SELECT
The most direct way to force Iceberg data from remote storage (S3/HDFS) onto your local disks is the CACHE SELECT command. This statement explicitly fetches data and populates the local block cache.
* To load an entire table:
sql
CACHE SELECT * FROM iceberg_catalog.your_db.your_table;
* To load only specific columns or recent data (to save disk space):
sql
CACHE SELECT column1, column2
FROM iceberg_catalog.your_db.your_table
WHERE event_date >= '2024-01-01';
2. Automating the Load (Scheduled Warmup)
If you want to ensure your Iceberg tables are always "hot" on disk (e.g., before morning BI reports), you can create a scheduled task:
sql
SUBMIT TASK warmup_iceberg_task
SCHEDULE EVERY (INTERVAL 1 DAY)
AS
CACHE SELECT * FROM iceberg_catalog.your_db.your_table;
3. Ensure Data Cache is Enabled
Before running the above, verify that your Compute Nodes (CN) or Backend nodes (BE) have the Data Cache enabled in their configuration (be.conf):
* starlet_use_star_cache = true (Enabled by default in v3.2.3+)
* `starlet_star_cache_disk_size_percent`: Check this value to ensure you have enough disk space to hold the tables you are loading.
4. Metadata Caching (Important for Iceberg)
For Iceberg specifically, the "planning" phase can be slow if metadata is not local. You can also enable Metadata Disk Caching in your FE configuration:
* Set enable_iceberg_metadata_disk_cache = true in fe.conf.
* This ensures that Iceberg manifest files and snapshots are also stored on the FE's local disk, speeding up the time it takes to even start the query.
Summary of Performance Metrics
When you run CACHE SELECT, StarRocks will return a summary:
* WRITE_CACHE_SIZE: How much data was actually written to your local disks.
* TOTAL_CACHE_USAGE: The percentage of your total allocated cache disk space currently in use.
Note: Since the cache uses an LRU (Least Recently Used) policy, if your Iceberg tables are larger than your total local disk cache capacity, older data will be evicted to make room for the data you are currently loading.
References
• data_source/block_cache_warmup.md
• deployment/shared_data/feature-support-shared-data.md
• data_source/catalog/iceberg/iceberg_catalog.md
• data_source/data_cache.md
)
• source/data_cache)
References
• data_source/block_cache_warmup.md
• data_source/catalog/iceberg/iceberg_catalog.mdKenneth Oh
08/11/2026, 6:27 AMRocky
08/11/2026, 6:28 AMCACHE SELECT): This only optimizes I/O. It brings the raw Parquet/ORC files from S3 to local disk. When you run a query, StarRocks still has to perform all the computation: it must decompress the Parquet blocks, filter the rows, perform joins, and calculate aggregations every single time.
* Materialized View: This optimizes I/O and Computation. An MV stores the result of the query. If your MV aggregates 4 billion rows into 1 million rows, StarRocks only reads the 1 million pre-calculated rows. It skips the heavy lifting of processing those 4 billion rows entirely.
2. Storage Format & Indexing
* Iceberg (Open Format): Even when cached, the data is in Parquet/ORC. StarRocks must use the Iceberg reader to parse it.
* MV (StarRocks Native Format): MVs are stored in StarRocks' internal Segment V2 format. This format is deeply integrated with the execution engine and supports:
* Primary/Sort Keys: Much faster data skipping (Zone Maps) than Parquet.
* Native Indexes: You can add Bitmap or Inverted indexes to an MV; you cannot add StarRocks-native indexes directly onto a remote Iceberg table.
3. Metadata Overhead
* Iceberg: Querying Iceberg requires parsing manifest files and snapshots to find which files to read. Even with metadata caching, this adds "planning" time.
* MV: Since the MV is a native StarRocks table, the FE (Frontend) knows exactly where the data is instantly. Metadata operations are significantly faster.
Comparison Summary
Feature | Cached Iceberg | Materialized View
Primary Benefit | Eliminates S3/HDFS latency | Eliminates computation + I/O
Data Format | Parquet/ORC (Remote Format) | Segment V2 (Native Format)
Computation | Full processing on every query | Pre-computed / Minimal processing
Optimization | Local Disk Cache (LRU) | Sort Keys, Bitmap Indexes, Pre-aggregation
Query Rewrite | No (You must query the table) | Yes (Optimizer automatically uses MV)
When to use which?
* Use Data Caching (Iceberg) when you have "ad-hoc" queries where users might filter by any column, and you want to ensure the raw data access isn't throttled by remote storage speeds.
* Use Materialized Views for your *"Golden Queries"*—the heavy, repetitive dashboards or reports where 2-minute wait times are unacceptable.
Pro-tip: You can actually create an Asynchronous MV on top of an Iceberg table. This gives you the best of both worlds: your "source of truth" stays in the open Iceberg format, but your high-performance queries hit the pre-computed StarRocks-native MV.
References
• data_source/data_cache.md
• using_starrocks/async_mv/Materialized_view.md
• data_source/catalog/iceberg/iceberg_catalog.md
• table_design/indexing.mdKenneth Oh
08/11/2026, 6:47 AMRocky
08/11/2026, 6:48 AMr8g.8xlarge typically provides up to 25 Gbps.
* Two r8g.4xlarge instances often provide 15 Gbps each, giving you an aggregate of 30 Gbps.
In a shared-data architecture, where nodes are constantly pulling data from S3 to populate the Data Cache, having more aggregate bandwidth prevents network saturation during high-concurrency peaks.
2. Distributed Data Cache I/O
In StarRocks shared-data mode, each Compute Node (CN) maintains its own local Data Cache (on NVMe/SSD).
* Parallelism: High QPS means many concurrent small reads. More instances mean more separate disk controllers and I/O queues working in parallel.
* Reduced Contention: Fewer threads on a smaller node will compete for the same local disk I/O, reducing "tail latency" (P99) for your queries.
3. More Efficient Concurrency Handling
For simple queries (point lookups or small aggregations), the bottleneck is often the overhead of thread scheduling and connection management.
* StarRocks is an MPP (Massively Parallel Processing) engine. It is designed to slice queries into fragments and distribute them.
* Distributing a high volume of small queries across a larger number of nodes reduces the CPU context-switching and lock contention that can occur when a single large node tries to handle too many concurrent tasks.
4. Better Elasticity and Availability
* Stateless Scaling: In a shared-data cluster, CN nodes are stateless. Adding or removing r8g.4xlarge nodes is instant and requires no data rebalancing. It is much easier to "fine-tune" your cluster capacity with smaller increments.
* Blast Radius: If an r8g.8xlarge node fails, you lose 50% of your capacity (assuming a 2-node setup). If one of four r8g.4xlarge nodes fails, you only lose 25%, making the cluster more resilient to high-traffic spikes.
When to reconsider (Larger Instances):
You should only move toward larger instances (r8g.8xlarge or 16xlarge) if:
1. Memory-Intensive Joins: You start running very large "Shuffle Joins" that require massive hash tables to fit in a single node's memory.
2. FE Overhead: You have so many small nodes (hundreds) that the Frontend (FE) struggles to manage the RPC heartbeats and query dispatching (this usually only happens at a very massive scale).
Recommendation
For high QPS with simple queries on r8g instances:
* Start with r8g.4xlarge.
* Ensure your Data Cache is configured to use the local NVMe storage of these instances.
* Monitor your *FE (Frontend) CPU*; if it spikes, it means the overhead of dispatching to many small nodes is becoming a factor (though unlikely at this instance size).
References
• deployment/shared_data/feature-support-shared-data.md
• introduction/Architecture.md
• administration/management/resource_management/Compute_node.md
• faq/shared_data_faq.md
References
• introduction/Architecture.md
• deployment/shared_data/feature-support-shared-data.mdKenneth Oh
08/13/2026, 5:55 AMRocky
08/13/2026, 5:55 AMexclusive_cpu_cores property.
Since this property requires a specific number of cores rather than a percentage, you must calculate 20% of your minimum BE core count. For example, if your BE nodes have 16 cores, 20% would be 3 cores (rounding to the nearest integer):
sql
CREATE RESOURCE GROUP reserved_20_group
PROPERTIES (
"exclusive_cpu_cores" = "3", -- 20% of a 16-core node
"mem_limit" = "20%" -- Memory is usually set as a %
);
* Best for: Critical production workloads that need absolute performance stability.
2. Soft Reservation (Weight-based)
If you want the group to get 20% of the CPU time when the system is busy, but allow other groups to use that CPU if the group is idle, use cpu_weight (formerly cpu_core_limit before v3.3.5).
In this mode, the share is calculated as: group_weight / total_weights_of_all_active_groups.
If you want this group to have 20% and the remaining groups to share 80%, you could set the weights like this:
sql
CREATE RESOURCE GROUP flexible_20_group
PROPERTIES (
"cpu_weight" = "20",
"mem_limit" = "20%"
);
-- Ensure other groups have weights that sum to 80
-- e.g., Altering the default group
ALTER RESOURCE GROUP default_rg
PROPERTIES (
"cpu_weight" = "80"
);
* Best for: Maximizing cluster utilization while ensuring fair access during peak hours.
Key Configuration Differences
Parameter | Type | Behavior
exclusive_cpu_cores | Hard Limit | Physically pins cores. Unavailable to others even if idle.
cpu_weight | Soft Limit | Proportional share. Idle resources can be borrowed by other groups.
Important Note on Memory: Unlike CPU, the mem_limit is almost always defined as a percentage (e.g., "0.2" or "20%") of the total queryable memory on the BE.
References
• administration/management/resource_management/resource_group.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.md
• faq/resource_isolation_faq.md
References
• faq/resource_isolation_faq.md
• administration/management/resource_management/resource_group.md
• administration/management/monitoring/metrics.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.mdKenneth Oh
08/13/2026, 5:57 AMRocky
08/13/2026, 5:58 AMcpu_weight parameter is restricted to a range between 0 and the average number of CPU cores available on your BE (Backend) nodes.
Based on the error message cpu_weight should range from 0 to 14, your cluster nodes have 14 CPU cores. Since 20 is greater than 14, the command failed.
How to fix it
In StarRocks, cpu_weight defines a relative share of the total CPU. To reserve 20% of the CPU resources, you should choose a value that represents 20% relative to the weights of your other resource groups.
Option 1: Using Weighted Shares (Recommended)
If you want this group to have 20% of the CPU and all other queries (the default_rg) to have 80%, you can use simple numbers that fit the 0–14 range:
* Set your group to 2.
* Set the default_rg to 8.
* Total weight = 10. Your group gets $2/10 = 20\%$.
sql
-- 1. Update your group to a valid weight (e.g., 2)
ALTER RESOURCE GROUP rg PROPERTIES (
"cpu_weight" = "2",
"mem_limit" = "20%"
);
-- 2. Update the default group to 8 to ensure the 20/80 ratio
ALTER RESOURCE GROUP default_rg PROPERTIES (
"cpu_weight" = "8"
);
Option 2: Using Core-Equivalent Weights
You can also set the weights to sum up to your total core count (14) to make the math easier to visualize:
* 20% of 14 cores = 2.8
* 80% of 14 cores = 11.2
sql
ALTER RESOURCE GROUP rg PROPERTIES (
"cpu_weight" = "2.8",
"mem_limit" = "20%"
);
Key Differences to Remember
1. Soft Limit (cpu_weight): This is a share. If the cluster is idle, this group can use more than 20%. If the cluster is under heavy load, it is guaranteed at least 20%.
2. Hard Limit (exclusive_cpu_cores): If you want a strict reservation where those cores are only for this group and cannot be shared even if idle, use exclusive_cpu_cores. The value would be an integer (e.g., 3 for ~20% of 14 cores). Note that the maximum value for exclusive_cpu_cores is min_be_cpu_cores - 1.
Correct Syntax Note
Ensure you use the PROPERTIES keyword and double quotes for the keys:
sql
ALTER RESOURCE GROUP rg PROPERTIES (
"cpu_weight" = "2",
"mem_limit" = "20%"
);
References
• administration/management/resource_management/resource_group.md
• sql-reference/sql-statements/cluster-management/resource_group/ALTER_RESOURCE_GROUP.md
• faq/resource_isolation_faq.md
References
• faq/resource_isolation_faq.md
• administration/management/resource_management/resource_group.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.md
• administration/management/monitoring/metrics.mdKenneth Oh
08/13/2026, 7:24 AMRocky
08/13/2026, 7:25 AMALTER RESOURCE GROUP ... ADD syntax. Note that each user needs their own set of parentheses:
sql
ALTER RESOURCE GROUP your_group_name
ADD
(user='alice'),
(user='bob'),
(user='charlie');
2. Using Roles (More Scalable)
If you have many users, it is often easier to assign them all to a specific Role and target that role instead of individual usernames. This way, any new user added to the role automatically inherits the resource limits.
sql
ALTER RESOURCE GROUP your_group_name
ADD (role='analytics_team');
3. Verification
To see which users and classifiers are currently associated with your resource group, use:
sql
SHOW RESOURCE GROUP your_group_name;
4. Removing Users
If you need to remove a specific user later, you must find the Classifier ID from the SHOW command above and then drop it:
sql
-- Replace 12345 with the actual Classifier ID found in SHOW command
ALTER RESOURCE GROUP your_group_name DROP (12345);
Key Rules
* Unique Classifiers: Each user-group mapping is a "classifier."
* Precedence: If a user matches multiple resource groups, StarRocks uses a weight-based system to pick the best match (more specific rules generally win).
* Session Variable: A user can always override their assigned group for the current session using SET resource_group = 'group_name'; if they have the necessary permissions.
References
• sql-reference/sql-statements/cluster-management/resource_group/ALTER_RESOURCE_GROUP.md
• administration/management/resource_management/resource_group.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.md
• sql-reference/sql-statements/cluster-management/resource_group/SHOW_RESOURCE_GROUP.md
References
• sql-reference/sql-statements/cluster-management/resource_group/ALTER_RESOURCE_GROUP.md
• administration/management/resource_management/resource_group.md
• sql-reference/sql-statements/cluster-management/resource_group/CREATE_RESOURCE_GROUP.mdKenneth Oh
08/14/2026, 4:35 AMRocky
08/14/2026, 4:35 AMKenneth Oh
08/14/2026, 4:53 PMRocky
08/14/2026, 4:53 PMenable_datacache_sharing = true in your session/global variables.
* Set the FE config enable_trace_historical_node = true.
* Data Cache Warmup: After scaling, you can proactively warm the cache for the MV so users don't hit the "cold" disks.
sql
-- Manually trigger a cache warmup for the MV
CACHE SELECT * FROM your_mv_name;
4. Automatic Scaling Note
If you are on StarRocks v3.3.4 or later, the parameter lake_enable_balance_tablets_between_workers is set to true by default, which ensures this balancing happens automatically. If you are on an older version, the balancing might only occur when the next refresh task or query is triggered.
Summary
* Does it redistribute? Yes, the responsibility for the MV's tablets is spread to the new nodes.
* Is data copied? No, the master data stays in S3. Only the local EBS cache needs to be "re-warmed" on the new nodes.
References
• administration/management/resource_management/Replica.md
• data_source/data_cache.md
• faq/shared_data_faq.md
• using_starrocks/async_mv/Materialized_view.md
References
• data_source/data_cache.md
• using_starrocks/caching/block_cache.md
• deployment/shared_data/feature-support-shared-data.md
• data_source/data_cache_troubleshooting.mdKenneth Oh
08/14/2026, 4:57 PMRocky
08/14/2026, 4:57 PMstop_cn.sh -g on the node receiving the warning.
* Behavior: The node will enter an "Exiting" state. It will finish its current query fragments but will reject new ones.
* Benefit: This allows the FE to proactively stop routing new queries to that node before it actually disappears, significantly reducing the number of failed queries compared to a sudden shutdown.
Summary of Behavior
Scenario | Impact on Availability | Impact on Performance | Client Action
Sudden Loss (Spot) | Tablets reassigned in < 10s | Slow (Cold Cache) | Must retry failed query
Graceful Exit (-g) | Seamless | Slow (Cold Cache) | No action needed
Recommendation
For a cluster using spot instances, it is highly recommended to:
1. Use a Load Balancer: Ensure your application connects via a load balancer or the FE list so it can easily reconnect.
2. Enable Graceful Shutdown: Use scripts to catch the spot termination signal and run stop_cn.sh -g.
3. Tablet Count: Ensure you have enough tablets (buckets) in your MV so that when one node fails, the tablets can be distributed evenly among the remaining nodes rather than overloading just one.
References
• introduction/Architecture.md
• administration/management/graceful_exit.md
• administration/management/BE_blacklist.md
• faq/shared_data_faq.md
References
• administration/management/resource_management/Replica.md
• administration/management/BE_blacklist.mdRocky
08/14/2026, 4:57 PMKenneth Oh
08/14/2026, 5:19 PMRocky
08/14/2026, 5:20 PMsql
SET GLOBAL enable_datacache_sharing = true;
-- Also requires FE config: enable_trace_historical_node = true
4. Proactive Cache Warmup
If you have a critical table and want to ensure it is always ready on the nodes that do own its tablets, you can use the Data Cache Warmup feature introduced in v3.3:
sql
-- This forces the responsible nodes to pull the data from S3 into their EBS cache
CACHE SELECT * FROM your_important_mv;
5. If "Shared-Nothing" is a better fit
If you have a table that is so critical it must reside on the local disk of every node for absolute maximum performance and redundancy, you would need to use a Shared-Nothing deployment (BEs instead of CNs) and set the replication_num to match the number of nodes in your cluster. This is generally not recommended if you have already committed to a Shared-Data architecture for its elasticity.
Summary
* Can you force a full copy on every node's EBS? No.
* Does it matter for performance? No, because Broadcast Joins handle this in-memory for queries.
* How to handle node failures/spot termination? Use Data Cache Sharing to pull "warm" data from survivors rather than S3.
References
• deployment/shared_data/feature-support-shared-data.md
• data_source/data_cache.md
• faq/shared_data_faq.md
• administration/management/resource_management/Replica.md
References
• deployment/shared_data/feature-support-shared-data.md
• deployment/shared_data/s3.md
• table_design/table_design.md
• knowledge/admin_and_operation/shared_data_compaction.md