メインコンテンツまでスキップ
バージョン: Candidate-3.5

クエリチューニングレシピ

実践的なプレイブック: 症状 → 根本原因 → 実証済みの修正
プロファイルを開いて問題のあるメトリックを見つけたが、「次に何をすべきか?」という問いに答える必要があるときに使用します。


1 · 迅速な診断ワークフロー

  1. 実行概要をざっと見る
    QueryPeakMemoryUsagePerNode > 80 % または QuerySpillBytes > 1 GB の場合、メモリとスピルのレシピに直接進みます。

  2. 最も遅いパイプライン / オペレーターを見つける
    Query Profile UISort by OperatorTotalTime % をクリックします。
    最も負荷の高いオペレーターが次に読むべきレシピブロックを教えてくれます(Scan, Join, Aggregate など)。

  3. ボトルネックのサブタイプを確認する
    各レシピはそのシグネチャメトリックパターンから始まります。修正を試みる前にそれらを一致させます。


2 · オペレーター別レシピ

2.1 OLAP / コネクタースキャン [metrics]

Scan Operator 内のさまざまなメトリックをよりよく理解するために、以下の図はこれらのメトリックとストレージ構造との関連を示しています。

profile_scan_relationship

ディスクからデータを取得し、述語を適用するために、ストレージエンジンはいくつかの技術を利用します:

  1. データストレージ: エンコードおよび圧縮されたデータは、さまざまなインデックスと共にセグメントに分けられてディスクに保存されます。
  2. インデックスフィルタリング: エンジンは、BitmapIndex、BloomfilterIndex、ZonemapIndex、ShortKeyIndex、NGramIndex などのインデックスを活用して不要なデータをスキップします。
  3. プッシュダウン述語: a > 1 のような単純な述語は、特定の列で評価されるようにプッシュダウンされます。
  4. 後期実体化: 必要な列とフィルタリングされた行のみがディスクから取得されます。
  5. 非プッシュダウン述語: プッシュダウンできない述語は評価されます。
  6. プロジェクション式: SELECT a + 1 のような式が計算されます。

Scan Operator は、IO タスクを実行するための追加のスレッドプールを利用します。したがって、このノードの時間メトリックの関係は以下に示されています。

profile_scan_time_relationship

一般的なパフォーマンスのボトルネック

コールドまたは遅いストレージBytesReadScanTime、または IOTaskExecTime が支配的で、ディスク I/O が 80‑100 % の範囲にある場合、スキャンはコールドまたは過小プロビジョニングされたストレージにヒットしています。ホットデータを NVMe/SSD に移動し、ストレージキャッシュを有効にするか、S3/HDFS をスキャンしている場合は remote_cache_capacity を増やします。

フィルタープッシュダウンの欠如PushdownPredicates が 0 に近いままで ExprFilterRows が高い場合、述語がストレージ層に到達していません。単純な比較として書き直す(%LIKE% や広範な OR チェーンを避ける)か、ゾーンマップ/Bloom インデックスやマテリアライズドビューを追加してプッシュダウンできるようにします。

スレッドプールの枯渇 – 高い IOTaskWaitTime と低い PeakIOTasks は、I/O スレッドプールが飽和していることを示します。BE の max_io_threads を増やすか、キャッシュを拡大してより多くのタスクを同時に実行できるようにします。

タブレット間のデータスキュー – 最大と最小の OperatorTotalTime の間に大きなギャップがある場合、一部のタブレットが他よりも多くの作業をしています。高いカーディナリティのキーで再バケット化するか、バケット数を増やして負荷を分散します。

Rowset/segment の断片化RowsetsReadCount/SegmentsReadCount が急増し、長い SegmentInitTime がある場合、多くの小さな rowset が存在します。手動でのコンパクションをトリガーし、小さなロードをバッチ処理してセグメントを事前にマージします。

累積されたソフトデリート – 大きな DeleteFilterRows は、ソフトデリートの使用が多いことを示します。BE コンパクションを実行してトゥームストーンを削除し、削除ビットマップを統合します。

2.2 集計 [metrics]

aggregation_operator
Aggregate Operator は、集計関数、GROUP BY、および DISTINCT の実行を担当します。

集計アルゴリズムの多様な形式

形式プランナーが選択する条件内部データ構造特徴 / 注意点
ハッシュ集計キーがメモリに収まる; カーディナリティが極端でないSIMD プロービングを備えたコンパクトなハッシュテーブルデフォルトパス、適度なキー数に最適
ソート集計入力が GROUP BY キーで既に順序付けされている単純な行比較 + 実行状態ハッシュテーブルコストがゼロ、プロービングが重いスキューで通常 2-3 倍速い
スピル可能な集計 (3.2+)ハッシュテーブルがメモリ制限を超えるディスクスピルパーティションを持つハイブリッドハッシュ/マージOOM を防ぎ、パイプラインの並行性を維持

多段階分散集計

StarRocks では、集計は分散方式で実装されており、クエリパターンとオプティマイザの決定に応じて多段階になることがあります。

┌─────────┐        ┌──────────┐        ┌────────────┐        ┌────────────┐
│ Stage 0 │ local │ Stage 1 │ shard/ │ Stage 2 │ gather/│ Stage 3 │ final
│ Partial │───► │ Update │ hash │ Merge │ shard │ Finalize │ output
└─────────┘ └──────────┘ └────────────┘ └────────────┘
ステージ使用される条件実行内容
ワンステージDISTRIBUTED BYGROUP BY のサブセットで、パーティションがコロケートされている部分集計が即座に最終結果になります。
ツーステージ (ローカル + グローバル)一般的な分散 GROUP BY各 BE 内の Stage 0 が重複を適応的に圧縮し、Stage 1 が GROUP BY に基づいてデータをシャッフルしてからグローバル集計を実行します。
スリーステージ (ローカル + シャッフル + ファイナル)重い DISTINCT と高カーディナリティの GROUP BY上記のように Stage 0; Stage 1 が GROUP BY によってシャッフルし、GROUP BYDISTINCT で集計; Stage 2 が部分状態を GROUP BY としてマージします。
フォーステージ (ローカル + 部分 + 中間 + ファイナル)重い DISTINCT と低カーディナリティの GROUP BY単一ポイントのボトルネックを避けるために GROUP BYDISTINCT によってシャッフルする追加のステージを導入します。

一般的なパフォーマンスのボトルネック

高カーディナリティの GROUP BYHashTableSize または HashTableMemoryUsage がメモリ制限に向かって膨らむ場合、グループ化キーが広すぎるか、非常に異なるものです。ソートされたストリーミング集計 (enable_streaming_preaggregation = true) を有効にし、ロールアップマテリアライズドビューを作成するか、広い文字列キーを INT にキャストします。

シャッフルスキュー – フラグメント間で HashTableSize または InputRowCount に大きな差がある場合、不均衡なシャッフルを示します。キーにソルト列を追加するか、DISTINCT [skew] ヒントを使用して行を均等に分配します。

状態が重い集計関数AggregateFunctions が実行時間を支配し、関数に HLL_BITMAP_、または COUNT(DISTINCT) が含まれる場合、巨大な状態オブジェクトが移動されています。取り込み中に HLL/ビットマップスケッチを事前に計算するか、近似バリアントに切り替えます。

部分集計の劣化 – 大きな InputRowCount と控えめな AggComputeTime、および上流の EXCHANGE での大規模な BytesSent は、事前集計がバイパスされたことを意味します。SET streaming_preaggregation_mode = "force_preaggregation" で強制的にオンに戻します。

高価なキー式ExprComputeTimeAggComputeTime に匹敵する場合、GROUP BY キーが行ごとに計算されています。これらの式をサブクエリで実体化するか、生成列に昇格させます。

2.3 ジョイン [metrics]

join_operator

Join Operator は、明示的なジョインまたは暗黙のジョインを実装する責任があります。

実行中、ジョインオペレーターはパイプラインエンジン内で並行して実行される Build (ハッシュテーブル構築) フェーズと Probe フェーズに分割されます。ベクトルチャンク (最大 4096 行) は SIMD でバッチハッシュされ、消費されたキーは実行時フィルター (Bloom または IN フィルター) を生成し、プローブ入力を早期に削減するために上流のスキャンにプッシュバックされます。

ジョイン戦略

StarRocks は、ベクトル化され、パイプラインに適したハッシュジョインコアに依存しており、コストベースオプティマイザがプラン時に評価する 4 つの物理戦略に組み込むことができます。

戦略オプティマイザが選択する条件高速化の要因
Colocate Join両方のテーブルが同じコロケーショングループに属している (同一のバケットキー、バケット数、レプリカレイアウト)。 ネットワークシャッフルなし: 各 BE はローカルバケットのみをジョインします。
Bucket-Shuffle Joinジョインテーブルの一方がジョインキーと同じバケットキーを持っているジョインテーブルの一方のみをシャッフルする必要があり、ネットワークコストを削減できます。
Broadcast Joinビルド側が非常に小さい (行/バイトのしきい値または明示的なヒント)。 小さなテーブルがすべてのプローブノードに複製され、大きなテーブルのシャッフルを回避します。
Shuffle (Hash) Join一般的なケース、キーが一致しない。各行をジョインキーでハッシュ分割し、プローブを BE 間で均等に分散します。

一般的なパフォーマンスのボトルネック

ビルド側のサイズ超過BuildHashTableTimeHashTableMemoryUsage のスパイクは、ビルド側がメモリを超えていることを示します。プローブ/ビルドテーブルを入れ替え、ビルドテーブルを事前フィルタリングするか、ハッシュスピリングを有効にします。

キャッシュに優しくないプローブSearchHashTableTime が支配的な場合、プローブ側がキャッシュ効率が悪いです。プローブ行をジョインキーでソートし、実行時フィルターを有効にします。

シャッフルスキュー – 単一のフラグメントの ProbeRows が他のすべてを圧倒する場合、データがスキューしています。より高いカーディナリティのキーに切り替えるか、key || mod(id, 16) のようなソルトを追加します。

意図しないブロードキャスト – ジョインタイプ BROADCAST で巨大な BytesSent は、小さいと思っていたテーブルが実際にはそうでないことを示します。broadcast_row_limit を下げるか、SHUFFLE ヒントでシャッフルを強制します。

実行時フィルターの欠如 – 小さな JoinRuntimeFilterEvaluate とフルテーブルスキャンは、実行時フィルターが伝播しなかったことを示します。ジョインを純粋な等式として書き直し、列タイプが一致していることを確認します。

非等式フォールバック – オペレータータイプが CROSS または NESTLOOP の場合、不等式または関数がハッシュジョインを妨げています。真の等式述語を追加するか、より大きなテーブルを事前にフィルタリングします。

2.4 Exchange (ネットワーク) [metrics]

サイズ超過のシャッフルまたはブロードキャストNetworkTime が 30 % を超え、BytesSent が大きい場合、クエリは過剰なデータを送信しています。ジョイン戦略を再評価するか、Exchange Compaction (pipeline_enable_exchange_compaction) を有効にします。

レシーバーのバックログ – 送信者キューが常に満杯である場合、シンクでの高い WaitTime はレシーバーが追いつけないことを示します。レシーバースレッドプール (brpc_num_threads) を増やし、NIC の帯域幅と QoS 設定を確認します。

2.5 ソート / マージ / ウィンドウ

さまざまなメトリックを理解しやすくするために、マージは以下の状態メカニズムとして表現できます。

               ┌────────── PENDING ◄──────────┐
│ │
│ │
├──────────────◄───────────────┤
│ │
▼ │
INIT ──► PREPARE ──► SPLIT_CHUNK ──► FETCH_CHUNK ──► FINISHED

|
| one traverse from leaf to root
|

PROCESS

ソートスピリングMaxBufferedBytes が約 2 GB を超えるか SpillBytes がゼロでない場合、ソートフェーズがディスクにスピルしています。LIMIT を追加し、上流で事前集計するか、マシンに十分なメモリがある場合は sort_spill_threshold を上げます。

マージの飢餓状態 – 高い PendingStageTime は、マージが上流のチャンクを待っていることを示します。最初にプロデューサーオペレーターを最適化するか、パイプラインバッファを拡大します。

広いウィンドウパーティション – ウィンドウオペレーター内の巨大な PeakBufferedRows は非常に広いパーティションまたはフレーム制限のない ORDER BY を示します。より細かくパーティション化し、RANGE BETWEEN 境界を追加するか、中間集計を実体化します。


3 · メモリ & スピル チートシート

閾値監視対象実践的なアクション
80 % の BE メモリQueryPeakMemoryUsagePerNodeセッションの exec_mem_limit を下げるか、BE RAM を追加します。
スピル検出 (SpillBytes > 0)QuerySpillBytes, オペレーターごとの SpillBlocksメモリ制限を増やす; SR 3.2+ にアップグレードしてハイブリッドハッシュ/マージスピルを使用します。

4 · ポストモーテムのテンプレート

1. 症状
– 遅いステージ: 集計 (OperatorTotalTime 68 %)
– 警告: HashTableMemoryUsage 9 GB (> exec_mem_limit)
2. 根本原因
– GROUP BY 高カーディナリティ UUID
3. 適用された修正
– ソートされたストリーミング集計 + ロールアップ MV を追加
4. 結果
– クエリ実行時間が 95 秒から 8 秒に短縮; メモリピーク 0.7 GB