このトピックでは、Query Profile を解釈して StarRocks インスタンスのパフォーマンスボトルネックを特定する方法と、クエリ効率を向上させるための最適化手法について説明します。
Query Profile の概要
Query Profile の可視化
StarRocks Manager を使用して Query Profile を視覚的に分析できます。詳細については、「Query Profile の概要」をご参照ください。
クエリボトルネックの特定
オペレーターが消費する時間が長いほど、その色は濃くなります。実行時間で上位 3 つのオペレーターがハイライト表示されるため、クエリのボトルネックを簡単に特定できます。
最適化のユースケース
Bitmap インデックス
Bitmap インデックスは、ビット配列を使用する特殊なタイプのデータベースインデックスです。配列内の各ビットは、データテーブル内の単一の行に対応します。ビットの値 (0 または 1) は、対応する行の値によって決まります。
-
gender列など、カーディナリティが低く、繰り返し値が多い列のクエリパフォーマンスを向上させるには、Bitmap インデックスを使用します。 -
クエリが Bitmap インデックスにヒットしたかどうかを確認するには、Query Profile の
BitmapIndexFilterRowsフィールドを表示します。
インデックスの作成
-
テーブル作成時に Bitmap インデックスを作成します。
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 INDEXを使用して、既存のテーブルに Bitmap インデックスを作成します。CREATE INDEX index_name ON table_name (column_name) [USING BITMAP] [COMMENT ''];
インデックス作成進捗の確認
SHOW ALTER TABLE COLUMN [FROM db_name];
インデックスの表示
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 INDEX index_name ON [db_name.]table_name;
単一列クエリのテスト
-
s_gender列をフィルターするクエリを実行します。select * from student_info where s_gender='male'; -
プロファイルを表示します。
[OLAP_SCAN] をクリックし、右側の Node Details タブをクリックします。メトリックを
Bitmapでフィルターして、Bitmap インデックスが有効になったことを確認します。
Bloom filter インデックス
Bloom filter インデックスは、データファイルにターゲットデータが含まれているかどうかを迅速に判断します。含まれていない場合、ファイルはスキップされ、スキャンされるデータ量が削減されます。Bloom filter はスペース効率が高く、ID 列などの高カーディナリティ列に適しています。
-
Primary Key モデルと Duplicate モデルでは、任意の列に Bloom filter インデックスを作成できます。Aggregate モデルと Update モデルでは、キー列にのみ Bloom filter インデックスを作成できます。
-
Bloom filter インデックスは、TINYINT、FLOAT、DOUBLE、または DECIMAL データ型の列ではサポートされていません。
-
Bloom filter インデックスは、
inまたは=述語を含むクエリ (例:SELECT ... WHERE ... IN ()およびSELECT ... WHERE column = ...) のパフォーマンスのみを向上させます。 -
クエリが Bloom filter インデックスにヒットしたかどうかを確認するには、Query Profile の
BloomFilterFilterRowsフィールドを表示します。
インデックスの作成
テーブルを作成する際に、PROPERTIES 句で bloom_filter_columns を指定することで Bloom filter インデックスを作成できます。次の例はその方法を示しています。
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"); -- 複数のインデックス列はカンマ (,) で区切ります。
インデックスの表示
SHOW CREATE TABLE table1;
インデックスの変更
例:
-
列
v1に Bloom filter インデックスを追加します。ALTER TABLE table1 SET ("bloom_filter_columns" = "k1,k2,v1"); -
列
k2の Bloom filter インデックスを削除します。ALTER TABLE table1 SET ("bloom_filter_columns" = "k1"); -
table1からすべての Bloom filter インデックスを削除します。ALTER TABLE table1 SET ("bloom_filter_columns" = "");
例
-
TPC-H の
customerテーブルを例にとります。ソートキーではない高カーディナリティ列 (c_phone列など) に Bloom filter インデックスを追加します。ALTER TABLE tpc_h_sf100.customer SET ("bloom_filter_columns" = "c_custkey, c_phone"); -
インデックスを表示して、Bloom filter インデックスが追加されたことを確認します。
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" ); | -
c_phone列をフィルターするクエリを実行します。select * from tpc_h_sf100.customer where c_phone = "10-334-921-5346"; -
プロファイルを表示します。
[OLAP_SCAN] をクリックし、右側の Node Details タブをクリックします。
BloomFilterFilterRowsメトリックを見つけて、Bloom filter インデックスがアクティブであることを確認します。
データスキューの最適化
この例では、TPC-H の lineitem テーブルを使用し、不均一な分布を持つ列をバケットキーとして選択して、データスキューの問題を示します。この場合、l_tag 列の値の分布は不均一です。
-
テストデータを作成します。TPC-H の
lineitemテーブルに新しい列を追加し、分散キーとして使用します。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; -
クエリを実行して全表スキャンを実行します。
select count(1) from lineitem_tag; -
プロファイルを表示します。
[OLAP_SCAN] をクリックし、右側の Node タブに移動します。[MaxTime] と [MinTime] の下の [SCAN] 時間を比較します。時間が数桁異なる場合、データスキューが発生している可能性があります。
-
分散キーを再定義して、テーブル構造を最適化します。データスキューを解決するため、lineitem2 という名前の新しいテーブルを作成し、バケット化キーを l_tag から l_orderkey に変更します。次の文でテーブルを作成します。重大な変更点は 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" ); -
再度プロファイルを表示します。[MaxTime] と [MinTime] の下の [SCAN] 時間を比較します。データスキューの問題が緩和されたことがわかります。
単一テーブルのマテリアライズドビュー
StarRocks の単一テーブルのマテリアライズドビュー (Rollup とも呼ばれます) は、直接クエリできない特殊なタイプのインデックスです。データウェアハウスに多数の複雑なクエリや反復的なクエリが含まれている場合、単一テーブルのマテリアライズドビューを作成してそれらを高速化できます。
クエリのテスト
-
TPC-H の
lineitemテーブルを例にとり、クエリを実行します。select l_returnflag,l_linestatus,l_shipmode,sum(l_extendedprice) from lineitem group by l_returnflag,l_linestatus,l_shipmode; -
マテリアライズドビューが作成されていないため、最初のクエリは完了までに 1115 ms かかります。
-
プロファイルを表示します。
[OLAP_SCAN] をクリックし、右側の Node タブに移動します。Rollup が
lineitemテーブル自体をスキャンしたことがわかります。
マテリアライズドビューの作成
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;
マテリアライズドビューのヒット確認
-
EXPLAIN コマンドを使用して、クエリが単一テーブルのマテリアライズドビューにヒットしたかどうかを確認できます。
explain select l_returnflag,l_linestatus ,l_shipmode,sum(l_extendedprice) from lineitem group by l_returnflag,l_linestatus,l_shipmode;返された結果の
rollup: material_testは、クエリが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 -
プロファイルを表示します。
[OLAP_SCAN] をクリックします。右側の Node タブで、クエリがマテリアライズドビューにヒットし、クエリ時間が 0.05 ms に短縮されたことがわかります。
JoinRuntimeFilter がアクティブかどうかの確認
JOIN 操作の右テーブルがハッシュテーブルを構築すると、Runtime Filter が作成されます。このフィルターはクエリツリーの左側に送信され、可能な限りスキャンオペレーターにプッシュダウンされます。スキャンオペレーターの [ノード詳細] タブで、JoinRuntimeFilter に関連するメトリックを表示できます。
-
TPC-DS の query72.sql を例にとります。
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; -
プロファイルを表示します。
[OLAP_SCAN] をクリックし、右側の Node Details タブをクリックします。スキャンオペレーターが在庫テーブルをスキャンしたときに JoinRuntimeFilter がトリガーされたことがわかります。
Colocate Join
StarRocks で Colocate Join 機能を使用するには、テーブル作成時に Colocation Group (CG) にテーブルを割り当てる必要があります。同じ CG 内のテーブルは、同じ Colocation Group Schema (CGS) に従う必要があります。これにより、データが同じ BE ノードセットに分散されることが保証されます。結合列がバケットキーでもある場合、コンピュートノードはローカル結合のみを実行すればよくなります。これにより、ノード間のデータ転送時間が短縮され、クエリパフォーマンスが向上します。Shuffle Join や Broadcast Join とは異なり、Colocate Join はネットワークデータ転送を回避するため、クエリパフォーマンスが大幅に向上します。
コロケートテーブルの作成
StarRocks は、同じデータベース内のテーブルに対する Colocate Join 操作のみをサポートします。
CREATE TABLE tbl (k1 int, v1 int sum)
DISTRIBUTED BY HASH(k1)
BUCKETS 8
PROPERTIES(
"colocate_with" = "group1"
);
Colocation Group の削除
グループ内の最後のテーブルが完全に削除されると、グループも自動的に削除されます。完全な削除とは、テーブルがごみ箱から削除されることを意味します。通常、DROP TABLE コマンドを使用してテーブルを削除した後、デフォルトで 1 日間ごみ箱に残ってから完全に削除されます。
グループ情報の表示
たとえば、次のコマンドを実行してグループ情報を表示します。
SHOW PROC '/colocation_group';
このコマンドは次の情報を返します。
+-------------+--------------+----------+------------+----------------+----------+----------+
| GroupId | GroupName | TableIds | BucketsNum | ReplicationNum | DistCols | IsStable |
+-------------+--------------+----------+------------+----------------+----------+----------+
| 11912.11916 | 11912_group1 | 11914 | 8 | 3 | int(11) | true |
+-------------+--------------+----------+------------+----------------+----------+----------+
次の表に、各列の説明を示します。
|
列 |
説明 |
|
GroupId |
クラスター全体で一意のグループ識別子。最初の部分はデータベース ID、2 番目の部分はグループ ID です。 |
|
GroupName |
グループのフルネーム。 |
|
TableIds |
このグループに含まれるテーブル ID のリスト。 |
|
BucketsNum |
バケットの数。 |
|
ReplicationNum |
レプリカ数。 |
|
DistCols |
分散列のデータ型。 |
|
IsStable |
グループが安定しているかどうかを示します。 |
次のコマンドを実行して、特定のグループのデータ分布を表示できます。
SHOW PROC '/colocation_group/GroupId';
SHOW PROC '/colocation_group/11912.11916';
このコマンドは次の情報を返します。
+-------------+---------------------+
| 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)
グループプロパティの変更
ALTER TABLE tbl SET ("colocate_with" = "group_name");
-
TPC-H データを例として、次の操作を実行します。
use tpc_h_sf100; ALTER TABLE orders SET ("colocate_with" = "cg_tpc_orders"); ALTER TABLE lineitem SET ("colocate_with" = "cg_tpc_orders"); -
次のクエリを実行します。
select count(1) from orders as o join lineitem as l on o.o_orderkey = l.l_orderkey; -
StarRocks Manager UI のクエリの Query Plan タブで、Colocate Join がアクティブかどうかを確認できます。
colocateが true の場合、Colocate Join が有効になったことを示します。これは、実行計画のcolocate: trueで示されます。対応する実行計画のフラグメントは次のとおりです。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
バケットとパーティションプルーニングの確認
StarRocks Manager UI のクエリの Query Plan タブで、partition および tabletRatio パラメーターを表示して、パーティションプルーニングまたはバケットプルーニングがアクティブかどうかを確認できます。