Query Profile performance diagnostics and optimization case studies

Updated at:
Copy as MD

This topic explains how to interpret a Query Profile to pinpoint performance bottlenecks in your StarRocks instance and provides optimization techniques to improve query efficiency.

Query Profile overview

Visualize a Query Profile

You can use StarRocks Manager to visually analyze a Query Profile. For more information, see Query Profile introduction.

Identify query bottlenecks

The more time an operator consumes, the darker its color. The top three operators by execution time are highlighted, making it easy to identify query bottlenecks.

Optimization use cases

Bitmap index

A Bitmap index is a special type of database index that uses a bit array. Each bit in the array corresponds to a single row in the data table. The value of the bit, either 0 or 1, is determined by the value of the corresponding row.

  • Use a Bitmap index to improve query performance for columns that have low cardinality and a high number of repeating values, such as a gender column.

  • To check if a query hits a Bitmap index, view the BitmapIndexFilterRows field in the Query Profile.

Create an index

  • Create a Bitmap index when you create a table.

    CREATE TABLE `student_info` (
      `s_stukey` bigint(20) NULL COMMENT "",
      `s_name` varchar(65533) NULL COMMENT "",
      `s_gender` varchar(65533) NULL COMMENT "",
      INDEX index1 (s_gender) USING BITMAP COMMENT 'index1'
    ) ENGINE=OLAP
    DUPLICATE KEY(`s_stukey`)
    COMMENT "OLAP"
    DISTRIBUTED BY HASH(`s_stukey`);
    INSERT INTO student_info
    VALUES
        (001,'student#000000019','male'),
        (002,'student#000000020','male'),
        (003,'student#000000021','male'),
        (004,'student#000000022','female');
  • Create a Bitmap index on an existing table by using CREATE INDEX.

    CREATE INDEX index_name ON table_name (column_name) [USING BITMAP] [COMMENT ''];

Check the index creation progress

SHOW ALTER TABLE COLUMN [FROM db_name];

View an index

SHOW {INDEX[ES] | KEY[S] } FROM [db_name.]table_name [FROM db_name];
MySQL [db_test]> show index from student_info;
+------------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table                  | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+------------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| db_test.student_info   |            | index1   |              | s_gender    |           |             |          |        |      | BITMAP     | index1  |
+------------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
1 row in set (0.01 sec)

Drop an index

DROP INDEX index_name ON [db_name.]table_name;

Test a single-column query

  1. Execute a query that filters the s_gender column.

    select * from student_info where s_gender='male';
  2. View the Profile.

    Click OLAP_SCAN, then click the Node Details tab on the right. Filter the metrics for Bitmap to confirm that the Bitmap index has taken effect.

Bloom filter index

A Bloom filter index quickly determines whether a data file contains the target data. If it does not, the file is skipped, which reduces the amount of data scanned. A Bloom filter is space-efficient and well-suited for high-cardinality columns, such as ID columns.

  • In the Primary Key model and Duplicate model, you can create a Bloom filter index on any column. In the Aggregate model and Update model, you can create a Bloom filter index only on key columns.

  • Bloom filter indexes are not supported for columns of the TINYINT, FLOAT, DOUBLE, or DECIMAL data types.

  • A Bloom filter index only improves performance for queries that contain in or = predicates, such as SELECT ... WHERE ... IN () and SELECT ... WHERE column = ....

  • To check if a query hits a Bloom filter index, view the BloomFilterFilterRows field in the Query Profile.

Create an index

When you create a table, you can create a Bloom filter index by specifying bloom_filter_columns in the PROPERTIES clause. The following example shows how to do this.

CREATE TABLE table1
(
  k1 BIGINT,
  k2 LARGEINT,
  v1 VARCHAR(2048) REPLACE,
  v2 SMALLINT DEFAULT "10"
)
ENGINE = olap
PRIMARY KEY(k1, k2)
DISTRIBUTED BY HASH (k1, k2) BUCKETS 10
PROPERTIES("bloom_filter_columns" = "k1,k2"); -- Separate multiple index columns with commas (,).

View an index

SHOW CREATE TABLE table1;

Modify an index

For example:

  • Add a Bloom filter index for column v1.

    ALTER TABLE table1 SET ("bloom_filter_columns" = "k1,k2,v1");
  • Remove the Bloom filter index for column k2.

    ALTER TABLE table1 SET ("bloom_filter_columns" = "k1");
  • Delete all Bloom filter indexes from table1.

    ALTER TABLE table1 SET ("bloom_filter_columns" = "");

Example

  1. Take the customer table from TPC-H as an example. Add a Bloom filter index on a high-cardinality column that is not a sort key, such as the c_phone column.

    ALTER TABLE tpc_h_sf100.customer SET ("bloom_filter_columns" = "c_custkey, c_phone");
  2. View the index to confirm that the Bloom filter index has been added.

    SHOW CREATE TABLE tpc_h_sf100.customer;
    | customer | CREATE TABLE `customer` (
      `c_custkey` bigint(20) NULL COMMENT "",
      `c_name` varchar(65533) NULL COMMENT "",
      `c_address` varchar(65533) NULL COMMENT "",
      `c_nationkey` bigint(20) NULL COMMENT "",
      `c_phone` varchar(65533) NULL COMMENT "",
      `c_acctbal` double NULL COMMENT "",
      `c_mktsegment` varchar(65533) NULL COMMENT "",
      `c_comment` varchar(65533) NULL COMMENT "",
      `Gender` varchar(65533) NULL DEFAULT "default_value" COMMENT ""
    ) ENGINE=OLAP
    DUPLICATE KEY(`c_custkey`)
    COMMENT "OLAP"
    DISTRIBUTED BY HASH(`c_custkey`) BUCKETS 24
    PROPERTIES (
    "replication_num" = "1",
    "bloom_filter_columns" = "c_custkey, c_phone",
    "in_memory" = "false",
    "storage_format" = "DEFAULT",
    "enable_persistent_index" = "false",
    "compression" = "LZ4"
    ); |
  3. Execute a query that filters the c_phone column.

    select * from tpc_h_sf100.customer where c_phone = "10-334-921-5346";
  4. View the Profile.

    Click OLAP_SCAN, then click the Node Details tab on the right. Find the BloomFilterFilterRows metric to confirm that the Bloom filter index is active.

Optimize for data skew

This example uses the lineitem table from TPC-H and selects a column with a non-uniform distribution as the bucketing key to demonstrate a data skew problem. In this case, the l_tag column has a non-uniform value distribution.

  1. Create test data. Add a new column to the lineitem table from TPC-H to use as the distribution key.

    CREATE TABLE `lineitem_tag` (
      `l_orderkey` bigint(20) NULL COMMENT "",
      `l_partkey` bigint(20) NULL COMMENT "",
      `l_suppkey` bigint(20) NULL COMMENT "",
      `l_linenumber` int(11) NULL COMMENT "",
      `l_quantity` double NULL COMMENT "",
      `l_extendedprice` double NULL COMMENT "",
      `l_discount` double NULL COMMENT "",
      `l_tax` double NULL COMMENT "",
      `l_returnflag` varchar(65533) NULL COMMENT "",
      `l_linestatus` varchar(65533) NULL COMMENT "",
      `l_shipdate` date NULL COMMENT "",
      `l_commitdate` date NULL COMMENT "",
      `l_receiptdate` date NULL COMMENT "",
      `l_shipinstruct` varchar(65533) NULL COMMENT "",
      `l_shipmode` varchar(65533) NULL COMMENT "",
      `l_comment` varchar(65533) NULL COMMENT "",
      `l_tag` varchar(65533)  default 'false' COMMENT ""
    ) ENGINE=OLAP
    DUPLICATE KEY(`l_orderkey`)
    COMMENT "OLAP"
    DISTRIBUTED BY HASH(`l_tag`) BUCKETS 96
    PROPERTIES (
    "replication_num" = "1",
    "in_memory" = "false",
    "storage_format" = "DEFAULT",
    "enable_persistent_index" = "false"
    );
    insert into lineitem_tag  select *, 'false' as l_tag  from tpc_h_sf100.lineitem;
  2. Execute a query to perform a full table scan.

    select count(1) from lineitem_tag;
  3. View the Profile.

    Click OLAP_SCAN and go to the Node tab on the right. Compare the SCAN time under MaxTime and MinTime. If the times differ by several orders of magnitude, data skew might have occurred.

  4. Optimize the table structure by redefining the distribution key. Create a new table named lineitem2 and change the bucketing key from l_tag to l_orderkey to resolve the data skew. The following statement creates the table, where the critical change is DISTRIBUTED BY HASH(l_orderkey) BUCKETS 96.

    CREATE TABLE `lineitem2` (
      `l_orderkey` bigint(20) NULL COMMENT "",
      `l_partkey` bigint(20) NULL COMMENT "",
      `l_suppkey` bigint(20) NULL COMMENT "",
      `l_linenumber` int(11) NULL COMMENT "",
      `l_quantity` double NULL COMMENT "",
      `l_extendedprice` double NULL COMMENT "",
      `l_discount` double NULL COMMENT "",
      `l_tax` double NULL COMMENT "",
      `l_returnflag` varchar(65533) NULL COMMENT "",
      `l_linestatus` varchar(65533) NULL COMMENT "",
      `l_shipdate` date NULL COMMENT "",
      `l_commitdate` date NULL COMMENT "",
      `l_receiptdate` date NULL COMMENT "",
      `l_shipinstruct` varchar(65533) NULL COMMENT "",
      `l_shipmode` varchar(65533) NULL COMMENT "",
      `l_comment` varchar(65533) NULL COMMENT "",
      `l_tag` varchar(65533) NULL DEFAULT "default_value" COMMENT ""
    ) ENGINE=OLAP
    DUPLICATE KEY(`l_orderkey`)
    COMMENT "OLAP"
    DISTRIBUTED BY HASH(`l_orderkey`) BUCKETS 96
    PROPERTIES (
    "replication_num" = "1",
    "bloom_filter_columns" = "l_orderkey",
    "in_memory" = "false",
    "storage_format" = "DEFAULT",
    "enable_persistent_index" = "false",
    "compression" = "LZ4"
    );
  5. View the Profile again. Compare the SCAN time under MaxTime and MinTime. You can see that the data skew issue has been mitigated.

Single-table materialized view

A single-table materialized view (also known as a Rollup) in StarRocks is a special type of index that cannot be queried directly. If your data warehouse contains a large number of complex or repetitive queries, you can create a single-table materialized view to accelerate them.

Test a query

  1. Take the lineitem table from TPC-H as an example and execute a query.

    select l_returnflag,l_linestatus,l_shipmode,sum(l_extendedprice) from lineitem group by l_returnflag,l_linestatus,l_shipmode;
  2. The initial query takes 1115 ms to complete because no materialized view has been created.

  3. View the Profile.

    Click OLAP_SCAN and go to the Node tab on the right. You can see that the Rollup scanned the lineitem table itself.

Create a materialized view

CREATE MATERIALIZED VIEW material_test AS select l_returnflag,l_linestatus ,l_shipmode,sum(l_extendedprice) from lineitem group by l_returnflag,l_linestatus,l_shipmode;

Verify materialized view hit

  1. You can use the EXPLAIN command to check if the query hits the single-table materialized view.

    explain select l_returnflag,l_linestatus ,l_shipmode,sum(l_extendedprice) from lineitem group by l_returnflag,l_linestatus,l_shipmode;

    In the returned result, rollup: material_test indicates that the query hit the materialized view named material_test.

    0:OlapScanNode
         TABLE: lineitem
         PREAGGREGATION: OFF. Reason: null
         partitions=1/1
         rollup: material_test
         tabletRatio=96/96
         tabletList=15646,15648,15650,15652,15654,15656,15658,15660,15662,15664 ...
         cardinality=600037902
         avgRowSize=14.285666
         numNodes=0
  2. View the Profile.

    Click OLAP_SCAN. On the Node tab on the right, you can see that the query hits the materialized view and the query time is reduced to 0.05 ms.

Verify if JoinRuntimeFilter is active

When the right table in a JOIN operation builds a hash table, a Runtime Filter is created. This filter is sent to the left side of the query tree and pushed down to the scan operator whenever possible. You can view metrics related to JoinRuntimeFilter on the Node Details tab of the scan operator.

  1. Take query72.sql from TPC-DS as an example.

    select i_item_desc,
           w_warehouse_name,
           d1.d_week_seq,
           sum(case when p_promo_sk is null then 1 else 0 end) no_promo,
           sum(case when p_promo_sk is not null then 1 else 0 end) promo,
           count(*) total_cnt
     from inventory
     join catalog_sales on (cs_item_sk = inv_item_sk)
     join warehouse on (w_warehouse_sk=inv_warehouse_sk)
     join item on (i_item_sk = cs_item_sk)
     join customer_demographics on (cs_bill_cdemo_sk = cd_demo_sk)
     join household_demographics on (cs_bill_hdemo_sk = hd_demo_sk)
     join date_dim d1 on (cs_sold_date_sk = d1.d_date_sk)
     join date_dim d2 on (inv_date_sk = d2.d_date_sk)
     join date_dim d3 on (cs_ship_date_sk = d3.d_date_sk)
     left outer join promotion on (cs_promo_sk=p_promo_sk)
     left outer join catalog_returns on (cr_item_sk = cs_item_sk and cr_order_number = cs_order_number)
     where d1.d_week_seq = d2.d_week_seq
       and inv_quantity_on_hand < cs_quantity
       and d3.d_date > (cast(d1.d_date AS DATE) + interval '5' day)
       and hd_buy_potential = '>10000'
       and d1.d_year = 1999
       and cd_marital_status = 'D'
     group by i_item_desc,w_warehouse_name,d1.d_week_seq
     order by total_cnt desc, i_item_desc, w_warehouse_name, d1.d_week_seq
     limit 100;
  2. View the Profile.

    Click OLAP_SCAN, then click the Node Details tab on the right. You can see that the JoinRuntimeFilter was triggered when the scan operator scanned the inventory table.

Colocate Join

In StarRocks, to use the Colocate Join feature, you must assign tables to a Colocation Group (CG) when they are created. Tables within the same CG must follow the same Colocation Group Schema (CGS), which ensures their data is distributed across the same set of BE nodes. When the join column is also the bucketing key, compute nodes only need to perform a local join. This reduces data transfer time between nodes and improves query performance. Unlike Shuffle or Broadcast Joins, a Colocate Join avoids network data transfer, which significantly improves query performance.

Create a colocated table

Note

StarRocks supports Colocate Join operations only on tables within the same database.

CREATE TABLE tbl (k1 int, v1 int sum)
DISTRIBUTED BY HASH(k1)
BUCKETS 8
PROPERTIES(
    "colocate_with" = "group1"
);

Delete a Colocation Group

When the last table in a Group is permanently deleted, the Group is also automatically deleted. Permanent deletion means that the table is removed from the Recycle Bin. Typically, after a table is deleted by using the DROP TABLE command, it remains in the Recycle Bin for one day by default before it is permanently deleted.

View group information

For example, execute the following command to view group information.

SHOW PROC '/colocation_group';

The command returns the following information.

+-------------+--------------+----------+------------+----------------+----------+----------+
| GroupId     | GroupName    | TableIds | BucketsNum | ReplicationNum | DistCols | IsStable |
+-------------+--------------+----------+------------+----------------+----------+----------+
| 11912.11916 | 11912_group1 | 11914    | 8          | 3              | int(11)  | true     |
+-------------+--------------+----------+------------+----------------+----------+----------+

The following table describes the columns.

Column

Description

GroupId

The unique identifier of a group across the entire cluster. The first part is the database ID, and the second part is the group ID.

GroupName

The full name of the group.

TableIds

A list of table IDs included in this group.

BucketsNum

The number of buckets.

ReplicationNum

The number of replicas.

DistCols

The data type of the distribution columns.

IsStable

Indicates whether the group is stable.

You can execute the following command to view the data distribution of a specific group.

SHOW PROC '/colocation_group/GroupId';
SHOW PROC '/colocation_group/11912.11916';

The command returns the following information.

+-------------+---------------------+
| BucketIndex | BackendIds          |
+-------------+---------------------+
| 0           | 10002, 10004, 10003 |
| 1           | 10002, 10004, 10003 |
| 2           | 10002, 10004, 10003 |
| 3           | 10002, 10004, 10003 |
| 4           | 10002, 10004, 10003 |
| 5           | 10002, 10004, 10003 |
| 6           | 10002, 10004, 10003 |
| 7           | 10002, 10004, 10003 |
+-------------+---------------------+
8 rows in set (0.00 sec)

Modify group properties

ALTER TABLE tbl SET ("colocate_with" = "group_name");
  1. Using the TPC-H data as an example, perform the following operations.

    use tpc_h_sf100;
    ALTER TABLE orders SET ("colocate_with" = "cg_tpc_orders");
    ALTER TABLE lineitem SET ("colocate_with" = "cg_tpc_orders");
  2. Execute the following query.

    select count(1) from orders as o join lineitem as l on o.o_orderkey = l.l_orderkey;
  3. You can check whether the Colocate Join is active on the Query Plan tab for the query in the StarRocks Manager UI.

    When colocate is true, it indicates that Colocate Join has taken effect. This is indicated by colocate: true in the execution plan. The corresponding execution plan fragment is:

    2:HASH JOIN
    |  join op: INNER JOIN (COLOCATE)
    |  colocate: true
    |  equal join conjunct: 10: l_orderkey = 1: o_orderkey
    |
    |----1:OlapScanNode
    |       TABLE: orders
    |       PREAGGREGATION: ON
    |       PREDICATES: 1: o_orderkey IS NOT NULL
    |       partitions=1/1
    |       rollup: orders
    |       tabletRatio=96/96
    |       tabletList=12403,12405,12407,12409,12411,12413,12415,12417,12419,12421 ...
    |       cardinality=135000000
    |       avgRowSize=1.0
    |       numNodes=0

Verify bucket and partition pruning

On the Query Plan tab for your query in the StarRocks Manager UI, you can view the partition and tabletRatio parameters to verify if partition pruning or bucket pruning is active.