始める前に
nyc_taxi.trips_small_inferredテーブルを作成してロードします。
サンプルデータセットをセットアップする
サンプルデータセットをセットアップする
元のParquetファイルは約5.8 GBです。ネットワーク環境や利用可能なリソースによっては、読み込みに数分かかる場合があります。
ORDER BY ()を使用しているため、日付フィルターで読み取り時に順序キーを利用してデータを除外することはできません。この例はパフォーマンス目標としてではなく、比較手法の練習に使用してください。
仕組み
- 元のクエリを実行し、ベースラインとなる測定値を取得します。
GROUP BYは維持したまま、クエリの集計計算をcountに置き換え、ソートや出力フォーマットなどの後続処理を削除します。- グループ化を削除し、グループ化しない
countを実行して、スキャン、フィルタリング、JOIN で残る処理を概算します。
SELECT ブロックに適用します。同等のデータソースとフィルターを維持し、一度に1つずつ操作を削除して、変更のたびに実行計画を確認してください。
これらの差は診断のための推定値であり、ClickHouse の実行ステージを正確に測定したものではありません。クエリを変更すると、実行計画、読み取られるカラム、ステージ間で受け渡されるデータが変わる可能性があります。結果から仮説を立て、クエリログと
EXPLAIN で検証してください。再現可能なベースラインを確立する
- すべての比較で同じデータと時間範囲を使用できるよう、
FROM、JOIN、PREWHERE、WHERE句は変更しないでください。 - 同程度のシステム負荷の下で、クエリの各バージョンを複数回実行します。
- キャッシュ条件を統一します。測定を記録する前に各バージョンのクエリを実行するか、以下に示すキャッシュを無効にしてください。キャッシュありの実行とキャッシュなしの実行を比較しないでください。
- 最速または最遅の結果に頼るのではなく、ウォームアップ実行後に繰り返し実行した結果の中央値など、代表的な所要時間を記録します。
- パフォーマンスの差を特定の変更に結び付けられるよう、一度に変更する変数は1つだけにします。
countが、比較対象のscanを回避する最適化済みの実行計画を使用しないよう、暗黙的なプロジェクションも無効にします。
これらの
SET ステートメントは、現在のセッションにのみ適用されます。すべての比較クエリをそのセッションで実行するか、すべての実行に同じ設定を適用してください。ファイルシステムキャッシュの設定を変更しても、オペレーティングシステムのページキャッシュやすべての ClickHouse キャッシュが無効になるわけではありません。完了したら、専用セッションを閉じるか、各設定を以前の値に戻してください。-
各実行に一意のクエリ ID を割り当てるか、クエリインターフェイスで生成された ID を記録します。たとえば、繰り返し実行する場合は、
bottleneck-a-1、bottleneck-a-2、bottleneck-a-3と識別します。clickhouse-clientでは、クエリ実行時に--query_id your-query-idを指定します。 - 同じ条件下で各比較クエリを複数回実行します。ウォームアップ実行は、測定対象の実行とは分けてください。
-
最近完了したクエリをルックアップする前に、クエリログをフラッシュします。
SYSTEM FLUSH LOGSを実行できない場合は、クエリログが自動的にフラッシュされるまで待ってから、ルックアップを再試行してください。レコードが表示されない場合は、クエリログが有効であること、system.query_logを読み取れること、クエリを実行したノードに対してクエリを実行していることを確認してください。 -
各クエリ ID に対応する完了レコードをルックアップします。
system.query_logには、完了したクエリのQueryStartイベントとQueryFinishイベントの両方が記録されます。最終的な実行時間、読み取り行数とバイト数、ピークメモリが含まれるQueryFinishでフィルタリングします。 -
クエリの各バージョンについて、測定対象の実行の実行時間の中央値を使用します。測定値を実際の実行に対応付けたままにするため、その中央値に最も近い実行から
read_rows、read_bytes、ピークメモリを記録します。
分散クエリの場合、開始元クエリの
QueryFinish レコードにある memory_usage は、クラスター全体のピーク値ではありません。initial_query_id を使用して、参加ノード上の子 QueryFinish レコードを確認してください。system.query_log を参照してください。
- テーブル
- CSV
クエリを段階的に単純化して実行する
GROUP BY が含まれない場合は、以下で説明するように実行 B をスキップしてください。
1
実行 A: 元のクエリを測定する
フィルター、グループ化、集計式、ソート、出力を変更せずに、完全なクエリを実行します。これにより、基準となる実行時間、読み取り行数とバイト数、ピークメモリ使用量を把握できます。このクエリは乗車記録を支払いタイプ別にグループ化し、複数の集計値を計算します。クエリの測定値を実行 A として記録します。
2
実行 B: count を使用してグループ化を維持する
クエリの 実行 B でも、データのスキャンとフィルタリング、JOIN の実行、グループの構築が行われます。実行時間を実行 A と比較して、元の集計式と集計後の処理の寄与を見積もります。また、集計式を削除すると読み取るカラムが減る可能性があるため、
FROM、JOIN、PREWHERE、WHERE、およびグループ化キーを維持します。集計式をグループ化された count に置き換えます。元のソートや出力式を含め、集計後の処理を削除します。read_bytes も比較します。元のクエリに GROUP BY が含まれない場合、分離するグループ化ステージはありません。実行 B をスキップし、元のクエリを直接実行 C と比較します。3
実行 C: グループ化を削除する
GROUP BY を削除し、単一の count を返します。残る処理を比較可能にするため、FROM、JOIN、PREWHERE、および WHERE 句は変更しません。read_bytes も比較します。返される count は、維持したフィルターと JOIN を通過して集計に到達する行数を示します。実行 C を解釈する前に、実行計画が意図したデータソースを読み取り、維持したフィルターを適用していることを確認します。プロジェクションやメタデータに基づく count によって、実行される処理が変わる可能性があります。スキャンベースの基準を得るには、3 つの実行すべてで、プランに示されている最適化を無効にします。暗黙的なプロジェクションには optimize_use_implicit_projections = 0、明示的なプロジェクションには optimize_use_projections = 0、テーブルメタデータから取得されるフィルターなしの count には optimize_trivial_count_query = 0 を使用します。実行 C が依然として遅い場合は、まずスキャンとフィルタリングから、そこに残っている操作を調査します。クエリを変更する前に、クエリログと EXPLAIN を使用して、疑われるボトルネックを検証します。差異を解釈する
読み取った行数を count の結果と比較する
read_rows を、その count の戻り値と比較します。たとえば、read_rows が 1 億で count が 100 万を返した場合、ClickHouse はカウントされた 1 行あたり約 100 行のソース行をスキャンしたことになります。これは、フィルターによってテーブルから読み取られた行の大半が除外されたことを示しますが、その理由はわかりません。この比率は、単純な単一テーブルスキャンを対象としています。複数のデータソースまたはプロジェクションを含むクエリでは、代わりに実行計画を用いて read_rows を解釈してください。
ClickHouse 25.9 以降では、索引の使用状況を確認する前に、クエリ条件キャッシュとデータスキッピングインデックスの動的適用を無効にします。
EXPLAIN indexes = 1 を使用して、ClickHouse が使用した索引と、各索引によって除外されたパーツおよびグラニュールの数を確認します。ClickHouse が想定より多くのグラニュールを選択した場合は、フィルターがテーブルのソートキーに合致しているか、パーティションプルーニングやデータスキッピングインデックスによってさらに多くのグラニュールを除外できるかを確認してください。プランに Indexes セクションがない場合、そのクエリについて EXPLAIN は索引プルーニングを報告していません。これに対し、テーブル全体を対象とする分析クエリでは、テーブルの大部分を読み取ることが想定されます。
想定されるボトルネックを検証する
- スキャンまたはフィルタリングのボトルネックについては、前述の設定で
EXPLAIN indexes = 1を使用し、ClickHouse が使用する索引と、各索引によって除外されるパーツおよびグラニュールの数を確認します。想定したスキャンではなく、実行計画で暗黙的なプロジェクションが使用されていないかも確認してください。 - グループ化または集約のボトルネックについては、関連するクエリプロファイルイベントとピークメモリ使用量を確認します。
- 実行 C が依然として遅く、JOIN を含む場合は、一度に 1 つの JOIN を削除した診断用クエリと比較します。所要時間が大幅に短縮される場合、削除した JOIN がかなりの処理負荷を生じさせていることを示唆します。JOIN を削除するとクエリの意味が変わるため、この比較は処理時間の切り分けにのみ使用し、行数の変化は別途解釈してください。
- 実行 C で維持されている別の操作にボトルネックがある場合は、実行計画と関連するクエリプロファイルイベントを確認します。
EXPLAIN が返す索引情報の詳細については、低速クエリ診断ガイドを参照してください。対象を絞った変更を 1 つ適用したら、同じ条件下で実行 A、B、C を繰り返します。変更によって意図した処理が削減され、ボトルネックが別の箇所に移っていないことを確認してください。