始める前に
nyc_taxi.trips_small_inferred テーブルを使用します。記載どおりに実行するには、まだ作成・ロードしていない場合は、テーブルを作成してロードしてください。
サンプルデータセットを設定する
サンプルデータセットを設定する
元のParquetファイルは約5.8 GBです。ネットワーク環境や利用可能なリソースによっては、読み込みに数分かかる場合があります。
アプローチを選ぶ
証拠がこれらのカテゴリのいずれにも当てはまらない場合は、クエリを無理にいずれかのアプローチに当てはめるのではなく、クエリプランに戻ってください。
読み取るデータを減らす
- 使用する場合: クエリがwide パーツのカラムや不要なカラムを読み取っている場合。
- 変更: クエリが読み取るカラムのサイズまたは数を減らします。
- 検証: 同じ条件で
read_bytes、メモリ使用量、実行時間を比較します。
カラム型を見直す
String ではなく数値型や日付型を使用し、想定される範囲を安全に表現できる最小の符号付きまたは符号なし数値型を選択します。時系列カラムには、Date32 または DateTime64 が提供するより広い範囲や小数秒精度が必要な場合を除き、Date または DateTime を使用します。
nullable カラムは慎重に使用する
Nullable カラムは、値に加えて個別の null マスクも保存するため、ClickHouse はこれも読み取り、処理する必要があります。null 値と型のデフォルト値を区別することに意味がある場合に使用します。カラムに必ず値が含まれる場合は、非 nullable 型を使用することでこの追加処理を回避できます。
カラムを変更する前に、現在観測されている非 null データが今後も常に非 null であると想定せず、ログソースデータとインジェストパスを確認してください。最適化の実例では、null 値を含むカラムを特定し、スキーマ変更の効果を測定する方法を示しています。
繰り返し出現する値には dictionary encoding を使用する
LowCardinality は dictionary encoding を使用しており、ステータス値、国コード、行数に比べて一意の値が大幅に少ないその他の次元など、String 型のカラムで効果を発揮することがよくあります。候補を特定する際の目安として、一意の値が約 10,000 個であることは有用ですが、固定の上限ではありません。識別子や、ほとんどの値が一意であるカラムは避け、型の変更前後で測定結果を比較してください。
より詳しいガイダンスについては、データ型の選択を参照してください。
必要なカラムだけを読み取る
SELECT * ではなく必要なカラムを明示的に指定してください。
system.query_log の read_bytes を使用して、選択するカラムを絞り込む前後の読み取りデータ量を比較します。read_bytes が依然として高い場合は、追加のカラムを必要とする式、フィルター、結合、ネストされたクエリがないか、クエリプランを確認してください。
たとえば、ダッシュボードで乗車時刻、支払いタイプ、合計金額だけが必要な場合は、行全体ではなく、それらのカラムを選択します。
SELECT * とこのクエリを比較してください。返される行数は変わりませんが、read_bytes には読み取るカラム数が少ないことが反映されるはずです。
データレイアウトをクエリに合わせる
- 使用する場面: 選択性の高いフィルターでも多数のパーツまたはグラニュールが読み取られる場合。
- 変更: 繰り返し実行されるクエリで使用するフィルターに合わせて、物理レイアウトを調整します。
- 検証:
EXPLAIN indexes = 1で選択されるパーツとグラニュールを比較し、read_rows、read_bytes、実行時間を確認します。
ソートキーから始める
MergeTree ファミリーのテーブルでは、ソートキーによって行のディスク上での配置が決まります。デフォルトでは、ソートキーはスパースプライマリインデックスを定義する主キーも兼ねます。OLTP データベースの主キーとは異なり、ClickHouse の主キーは一意性を保証しません。ClickHouse は、クエリのフィルター条件を満たし得ないグラニュールをスキップできるため、パフォーマンスが向上します。
選択性の高いフィルターで頻繁に使用されるカラムを優先し、キー内での順序も考慮します。関連する値をまとめることで、圧縮率も向上する場合があります。クエリのグループ化またはソート順がキーと一致する場合、ClickHouse は GROUP BY または ORDER BY に対して順序を活用した最適化を使用できることがあります。
異なるソートキーをテストする前後で、EXPLAIN indexes = 1 で選択されるパーツとグラニュールを比較します。また、同じ条件下で read_rows、read_bytes、および所要時間も比較します。選択に関する詳細なガイダンスについては、主キーの選択を参照してください。
この例のテーブルでは ORDER BY () を使用しているため、次の選択性の高い日付フィルターでグラニュールを除外できるソートキーがありません。
ClickHouse 25.9 以降では、これらの設定により、
EXPLAIN に使用された索引と、それらによって除外されたパーツおよびグラニュールが報告されます。pickup_datetime を含む順序キーを持つテーブルを作成し、それに対して同じ EXPLAIN を実行します。変更全体を所要時間やメモリの測定で評価する前に、プランのプライマリキーセクションで選択されるグラニュール数が減少しているはずです。
追加の索引とデータレイアウトの選択肢を評価する
EXPLAIN indexes = 1を使用して、クエリが実際にパーティションをプルーニングしていることを確認してください。
局所的なフィルターにデータスキップ索引を追加する
データスキップ索引は、フィルターに一致しないブロックの読み取りをClickHouseが回避できるようにするメタデータを保存します。順序キーが重要なフィルターをサポートしておらず、一致する値がブロック内で十分に局在している場合に特に有用です。
たとえば、ほとんどのブロックに検索対象の値が含まれていない場合、ブルームフィルター索引は等価検索に役立ちます。スキップ索引は、データ型と順序キーを確認したうえで使用してください。ブロックをほとんど除外できない索引は、処理量を大幅に削減しないまま、ストレージと評価のオーバーヘッドを増やします。代表的なデータで索引タイプと粒度をテストし、EXPLAIN indexes = 1を使用して選択されたグラニュールを比較し、read_rows、read_bytes、および所要時間を確認してください。
プロジェクションを選択的に使用する
プロジェクションは、テーブルとともに代替のデータレイアウトを保存します。別の順序キーや事前計算済みの結果を提供でき、クエリから直接参照しなくても、ClickHouseは適用可能なプロジェクションを選択できます。
たとえば、payment_typeで順序付けたプロジェクションは、基となるテーブル’の順序付けでは対応できない、繰り返し使用されるフィルターをサポートできます。基となる順序付けで効率的に対応できない重要なアクセスパターンには、少数のプロジェクションを使用してください。
プロジェクションは追加の索引やカラムデータを保存するため、挿入時とマージ時の処理量が増加します。全カラムプロジェクションでは、保存するカラムが複製されます。プロジェクションを多用すると、クエリ時に最適なプロジェクションを選択するための処理量も増える可能性があります。異なるアクセスパターンが多数ある大規模なデプロイメントでは、プロジェクションを減らすか、用途に特化した個別のテーブルを使用する方が、多くの場合運用しやすくなります。これらのメカニズムの選択については、materialized view とプロジェクションの比較を参照してください。
ソーステーブルへのクエリを継続しながら、支払いタイプと乗車時刻でフィルターするクエリ用に代替の順序付けを追加します。
EXPLAIN projections = 1 を使用して、ClickHouse が projection を選択し、読み取り行数またはバイト数が減るかを確認します。このパターンを広く適用する前に、insert 時のオーバーヘッドとストレージオーバーヘッドも測定してください。
繰り返し実行する処理を事前計算する
- 使用する場面: 同じ変換や集計がクエリ時間の大部分を繰り返し占める場合。
- 変更: 繰り返し実行する計算を、インジェスト時、定期的なリフレッシュ、または用途に特化したデータレイアウトに移します。
- 検証: インジェストまたはリフレッシュの負荷が許容範囲内に収まっていること、およびクエリがより小さい結果セットを読み取り、クエリ時の計算量が減っていることを確認します。
各セクションでは、基本的な実装方法、主な運用上のトレードオフ、結果の検証方法を説明します。
インクリメンタルmaterialized view
pickup_date でグループ化した sum(trip_count) をクエリして、バックグラウンドマージ待ちの行をクエリ時に結合します。ビューは新規挿入のみを処理するため、既存のソースデータは別途バックフィルしてください。実行時間と読み取り行数を元の集約と比較して変更を検証し、追加の挿入処理が許容範囲内であることを確認します。
リフレッシャブルmaterialized view
system.view_refreshes を確認して、更新の所要時間、ステータス、頻度がワークロードに適していることを確認します。
用途別テーブル
Nullable を削除します。この方法がワークロードのデータ要件に適していることを確認してください。ダッシュボードではこのテーブルを明示的にクエリする必要があり、インジェストパイプラインで最新の状態に維持する必要があります。ソーステーブルへのクエリと、読み取り行数・バイト数、メモリ使用量、所要時間を比較して、変更を検証してください。追加で必要となるストレージとパイプラインの保守も、判断材料に含めてください。