クエリ分析
クエリパフォーマンスを最適化する方法は、よくある質問です。遅いクエリはユーザーエクスペリエンスやクラスターのパフォーマンスを損ないます。クエリパフォーマンスを分析し、最適化することが重要です。
クエリ情報は fe/log/fe.audit.log
で確認できます。各クエリには QueryID
が対応しており、これを使用してクエリの QueryPlan
と Profile
を検索できます。QueryPlan
は SQL ステートメントを解析して FE が生成する実行プランです。Profile
は BE の実 行結果で、各ステップで消費された時間や処理されたデータ量などの情報を含みます。
プラン分析
StarRocks では、SQL ステートメントのライフサイクルはクエリ解析、クエリプランニング、クエリ実行の3つのフェーズに分けられます。分析ワークロードに必要な QPS が高くないため、クエリ解析は一般的にボトルネックにはなりません。
StarRocks のクエリパフォーマンスは、クエリプランニングとクエリ実行によって決まります。クエリプランニングはオペレーター (Join/Order/Aggregate) を調整し、クエリ実行は特定の操作を実行します。
クエリプランは、DBA がクエリ情報にアクセスするためのマクロな視点を提供します。クエリプランはクエリパフォーマンスの鍵であり、DBA が参照するための良いリソースです。以下のコードスニペットは、TPCDS query96
を例にとり、クエリプランの確認方法を示しています。
クエリのプランを確認するには、 EXPLAIN ステートメントを使用します。
EXPLAIN select count(*)
from store_sales
,household_demographics
,time_dim
, store
where ss_sold_time_sk = time_dim.t_time_sk
and ss_hdemo_sk = household_demographics.hd_demo_sk
and ss_store_sk = s_store_sk
and time_dim.t_hour = 8
and time_dim.t_minute >= 30
and household_demographics.hd_dep_count = 5
and store.s_store_name = 'ese'
order by count(*) limit 100;
クエリプランには、論理クエリプランと物理クエリプランの2種類があります。ここで説明するクエリプランは、論理クエリプランを指します。TPCDS query96.sql
に対応するクエリプランは以下の通りです。
+------------------------------------------------------------------------------+
| Explain String |
+------------------------------------------------------------------------------+
| PLAN FRAGMENT 0 |
| OUTPUT EXPRS:<slot 11> |
| PARTITION: UNPARTITIONED |
| RESULT SINK |
| 12:MERGING-EXCHANGE |
| limit: 100 |
| tuple ids: 5 |
| |
| PLAN FRAGMENT 1 |
| OUTPUT EXPRS: |
| PARTITION: RANDOM |
| STREAM DATA SINK |
| EXCHANGE ID: 12 |
| UNPARTITIONED |
| |
| 8:TOP-N |
| | order by: <slot 11> ASC |
| | offset: 0 |
| | limit: 100 |
| | tuple ids: 5 |
| | |
| 7:AGGREGATE (update finalize) |
| | output: count(*) |
| | group by: |
| | tuple ids: 4 |
| | |
| 6:HASH JOIN |
| | join op: INNER JOIN (BROADCAST) |
| | hash predicates: |
| | colocate: false, reason: left hash join node can not do colocate |
| | equal join conjunct: `ss_store_sk` = `s_store_sk` |
| | tuple ids: 0 2 1 3 |
| | |
| |----11:EXCHANGE |
| | tuple ids: 3 |
| | |
| 4:HASH JOIN |
| | join op: INNER JOIN (BROADCAST) |
| | hash predicates: |
| | colocate: false, reason: left hash join node can not do colocate |
| | equal join conjunct: `ss_hdemo_sk`=`household_demographics`.`hd_demo_sk`|
| | tuple ids: 0 2 1 |
| | |
| |----10:EXCHANGE |
| | tuple ids: 1 |
| | |
| 2:HASH JOIN |
| | join op: INNER JOIN (BROADCAST) |
| | hash predicates: |
| | colocate: false, reason: table not in same group |
| | equal join conjunct: `ss_sold_time_sk` = `time_dim`.`t_time_sk` |
| | tuple ids: 0 2 |
| | |
| |----9:EXCHANGE |
| | tuple ids: 2 |
| | |
| 0:OlapScanNode |
| TABLE: store_sales |
| PREAGGREGATION: OFF. Reason: `ss_sold_time_sk` is value column |
| partitions=1/1 |
| rollup: store_sales |
| tabletRatio=0/0 |
| tabletList= |
| cardinality=-1 |
| avgRowSize=0.0 |
| numNodes=0 |
| tuple ids: 0 |
| |
| PLAN FRAGMENT 2 |
| OUTPUT EXPRS: |
| PARTITION: RANDOM |
| |
| STREAM DATA SINK |
| EXCHANGE ID: 11 |
| UNPARTITIONED |
| |
| 5:OlapScanNode |
| TABLE: store |
| PREAGGREGATION: OFF. Reason: null |
| PREDICATES: `store`.`s_store_name` = 'ese' |
| partitions=1/1 |
| rollup: store |
| tabletRatio=0/0 |
| tabletList= |
| cardinality=-1 |
| avgRowSize=0.0 |
| numNodes=0 |
| tuple ids: 3 |
| |
| PLAN FRAGMENT 3 |
| OUTPUT EXPRS: |
| PARTITION: RANDOM |
| STREAM DATA SINK |
| EXCHANGE ID: 10 |
| UNPARTITIONED |
| |
| 3:OlapScanNode |
| TABLE: household_demographics |
| PREAGGREGATION: OFF. Reason: null |
| PREDICATES: `household_demographics`.`hd_dep_count` = 5 |
| partitions=1/1 |
| rollup: household_demographics |
| tabletRatio=0/0 |
| tabletList= |
| cardinality=-1 |
| avgRowSize=0.0 |
| numNodes=0 |
| tuple ids: 1 |
| |
| PLAN FRAGMENT 4 |
| OUTPUT EXPRS: |
| PARTITION: RANDOM |
| STREAM DATA SINK |
| EXCHANGE ID: 09 |
| UNPARTITIONED |
| |
| 1:OlapScanNode |
| TABLE: time_dim |
| PREAGGREGATION: OFF. Reason: null |
| PREDICATES: `time_dim`.`t_hour` = 8, `time_dim`.`t_minute` >= 30 |
| partitions=1/1 |
| rollup: time_dim |
| tabletRatio=0/0 |
| tabletList= |
| cardinality=-1 |
| avgRowSize=0.0 |
| numNodes=0 |
| tuple ids: 2 |
+------------------------------------------------------------------------------+
128 rows in set (0.02 sec)
クエリ 96 は、いくつかの StarRocks の概念を含むクエリプランを示しています。
名前 | 説明 |
---|---|
avgRowSize | スキャンされたデータ行の平均サイズ |
cardinality | スキャンされたテーブルのデータ行の総数 |
colocate | テーブルがコロケートモードかどうか |
numNodes | スキ ャンされるノードの数 |
rollup | マテリアライズドビュー |
preaggregation | 事前集計 |
predicates | 述語、クエリフィルター |
クエリ 96 のクエリプランは、0 から 4 までの番号が付けられた5つのフラグメントに分かれています。クエリプランは、下から上に順に読み取ることができます。
フラグメント 4 は time_dim
テーブルをスキャンし、関連するクエリ条件(すなわち time_dim.t_hour = 8 and time_dim.t_minute >= 30
)を事前に実行します。このステップは述語プッシュダウンとも呼ばれます。StarRocks は集計テーブルに対して PREAGGREGATION
を有効にするかどうかを決定します。前の図では、time_dim
の事前集計は無効になっています。この場合、time_dim
のすべてのディメンション列が読み取られ、テーブルに多くのディメンション列がある場合、パフォーマンスに悪影響を及ぼす可能性があります。time_dim
テーブルがデータ分割に range partition
を選択している場合、クエリプランでいくつかのパーティションがヒットし、無関係なパーティションは自動的にフィルタリングされます。マテリアライズドビューがある場合、StarRocks はクエリに基づいてマテリアライズドビューを自動的に選択します。マテリアライズドビューがない場合、クエリは自動的にベーステーブルをヒットします(例えば、前の図の rollup: time_dim
)。
スキャンが完了すると、フラグメント 4 は終了します。データは他のフラグメントに渡され、前の図の EXCHANGE ID : 09 に示されるように、受信ノードラベル 9 に渡さ れます。
クエリ 96 のクエリプランでは、フラグメント 2、3、4 は異なるテーブルをスキャンする役割を担っています。具体的には、クエリ内の Order/Aggregation/Join
操作はフラグメント 1 で実行されます。
フラグメント 1 は BROADCAST
メソッドを使用して Order/Aggregation/Join
操作を実行します。つまり、小さなテーブルを大きなテーブルにブロードキャストします。両方のテーブルが大きい場合は、SHUFFLE
メソッドを使用することをお勧めします。現在、StarRocks は HASH JOIN
のみをサポートしています。colocate
フィールドは、結合された2つのテーブルが同じ方法でパーティション分割およびバケッティングされていることを示すために使用され、データを移動せずにローカルでジョイン操作を実行できます。ジョイン操作が完了すると、上位レベルの aggregation
、order by
、および top-n
操作が実行されます。
特定の式を削除して(オペレーターのみを保持)、クエリプランをよりマクロな視点で提示することができます。以下の図に示すように。
クエリヒント
クエリヒントは、クエリオプティマイザにクエリの実行方法を明示的に示唆する指示またはコメントです。現在、StarRocks は3種類のヒントをサポートしています:システム変数ヒント (SET_VAR
)、ユーザー定義変数ヒント (SET_USER_VARIABLE
)、および Join ヒントです。ヒントは単一のクエリ内でのみ有効です。
システム変数ヒント
SET_VAR
ヒントを使用して、SELECT および SUBMIT TASK ステートメントで1つ以上の システム変数 を設定し、ステートメントを実行できます。また、CREATE MATERIALIZED VIEW AS SELECT および CREATE VIEW AS SELECT などの他のステートメントに含まれる SELECT 句で SET_VAR
ヒントを使用することもできます。なお、SET_VAR
ヒントが CTE の SELECT 句で使用されている場合、ステートメントが正常に実行されても SET_VAR
ヒントは有効になりません。
システム変数の一般的な使用法 と比較して、SET_VAR
ヒントはステートメントレベルで有効になり、セッション全体には影響しません。
構文
[...] SELECT /*+ SET_VAR(key=value [, key = value]) */ ...
SUBMIT [/*+ SET_VAR(key=value [, key = value]) */] TASK ...