JOINの最小化と最適化
ClickHouseは、さまざまなJOINタイプとアルゴリズムをサポートしており、最近のリリースではJOINのパフォーマンスが大幅に向上しています。ただし、JOINは本質的に単一の非正規化テーブルからのクエリよりもコストが高くなります。非正規化は、クエリ時間から挿入または前処理時間への計算作業をシフトさせるため、実行時のレイテンシが大幅に低下することが多いです。リアルタイムまたはレイテンシ感受性の高い分析クエリの場合は、非正規化が強く推奨されます。
一般的に、以下のような場合に非正規化を行うべきです:
- テーブルが頻繁に変更されない場合、またはバッチリフレッシュが許容される場合。
- 関係が多対多ではないか、基数が過度に高くない場合。
- クエリされるカラムの限定されたサブセットのみが必要な場合、つまり特定のカラムを非正規化から除外できる場合。
- Flinkのような上流システムに処理をシフトできる能力がある場合、リアルタイムでの強化やフラット化が管理できます。
すべてのデータを非正規化する必要はありません - よくクエリされる属性に焦点を当ててください。また、マテリアライズドビューを検討して、サブテーブル全体を複製するのではなく、逐次的に集計を計算することをお勧めします。スキーマの更新がまれであり、レイテンシが重要な場合、非正規化は最良のパフォーマンストレードオフを提供します。
ClickHouseでのデータの非正規化に関する完全なガイドはこちらを参照してください。
JOINが必要な場合
JOINが必要な場合は、少なくともバージョン24.12、できれば最新バージョンを使用してください。JOINのパフォーマンスは、新しいリリースごとに改善され続けています。ClickHouse 24.12以降、クエリプランナーは最適なパフォーマンスのために自動的に小さなテーブルをJOINの右側に配置します。このタスクは以前は手動で行う必要がありました。さらに、より侵攻的なフィルタープッシュダウンや複数のJOINの自動再配置が近日中に登場する予定です。
JOINのパフォーマンスを向上させるためのベストプラクティスを次の通りに実践してください:
- 直交積を避ける: 左側の値が右側の複数の値と一致する場合、JOINは複数の行を返します - いわゆる直交積です。右側のすべての一致が必要でなく、単一の一致だけが必要な場合は、
ANY
JOIN(例:LEFT ANY JOIN
)を使用できます。これらは通常のJOINよりも速く、メモリを少なく使用します。 - JOINされるテーブルのサイズを削減する: JOINのランタイムとメモリ消費は、左側と右側のテーブルのサイズに比例して増加します。JOINによって処理されるデータ量を減らすために、
WHERE
またはJOIN ON
句に追加のフィルタ条件を追加してください。ClickHouseはフィルタ条件をクエリプランのできるだけ深い位置にプッシュダウンします。フィルタが自動的にプッシュダウンされない場合(何らかの理由で)、JOINの一方をサブクエリとして再記述して強制的にプッシュダウンさせます。 - 適切な場合は辞書経由の直接JOINを使用する: ClickHouseの標準JOINは、2つのフェーズで実行されます。右側を反復してハッシュテーブルを構築するビルドフェーズの後、左側を反復してハッシュテーブルルックアップを通じて一致するJOINパートナーを見つけるプローブフェーズです。右側が辞書またはキーと値の特性を持つ別のテーブルエンジン(例:EmbeddedRocksDBやJoinテーブルエンジン)である場合、ClickHouseは「直接」JOINアルゴリズムを使用でき、ハッシュテーブルを構築する必要がなくなり、クエリ処理を高速化します。これは
INNER
またはLEFT OUTER
JOINに対して機能し、リアルタイムの分析ワークロードに最適です。 - JOINのためにテーブルのソートを活用する: ClickHouseの各テーブルは、テーブルの主キーのカラムによってソートされています。
full_sorting_merge
やpartial_merge
のようなソートマージJOINアルゴリズムを使用してテーブルのソートを利用できます。ハッシュテーブルに基づく標準のJOINアルゴリズム(以下のparallel_hash
、hash
、grace_hash
を参照)とは異なり、ソートマージJOINアルゴリズムはまずソートを行い、次に両方のテーブルをマージします。クエリがそれぞれの主キーのカラムで両方のテーブルをJOINする場合、ソートステップが省略される最適化があります。 - ディスクスピルJOINを避ける: JOINの中間状態(例:ハッシュテーブル)は、大きくなりすぎて主メモリに収まらなくなることがあります。この場合、ClickHouseはデフォルトでアウトオブメモリーエラーを返します。一部のJOINアルゴリズム(下記参照)、例えば
grace_hash
、partial_merge
、full_sorting_merge
などは、中間状態をディスクにスピルしてクエリの実行を続けることができます。ただし、ディスクアクセスがJOIN処理を大幅に遅くする可能性があるため、これらのJOINアルゴリズムは慎重に使用すべきです。代わりに中間状態のサイズを減らすために他の方法でJOINクエリを最適化することをお勧めします。 - 外部JOINにおけるデフォルト値を不一致マーカーとして使用する: 左/右/完全外部JOINは、左/右/両方のテーブルからすべての値を含みます。他のテーブルで特定の値に対するJOINパートナーが見つからない場合、ClickHouseはJOINパートナーを特別なマーカーで置き換えます。SQL標準では、データベースがNULLをそのようなマーカーとして使用することが義務付けられています。ClickHouseでは、結果カラムをNullableでラップする必要があり、追加のメモリとパフォーマンスオーバーヘッドが発生します。代替案として、
join_use_nulls = 0
の設定を構成し、結果カラムのデータ型のデフォルト値をマーカーとして使用できます。
ClickHouseでJOINに辞書を使用する際は、辞書が設計上、重複キーを許可しないことを理解することが重要です。データの読み込み中、重複キーは静かに重複削除され、特定のキーに対して最後に読み込まれた値のみが保持されます。この動作により、辞書は一対一または多対一の関係に理想的であり、最新または公的な値のみが必要です。しかし、一対多または多対多の関係(例:役者に役割を結合する場合、役者が複数の役割を持つ可能性がある)で辞書を使用すると、すべての一致する行のうち1つを除いて静かにデータが失われます。そのため、辞書は複数の一致を通じて完全な関係の忠実度を要求されるシナリオには適していません。
適切なJOINアルゴリズムの選択
ClickHouseは、スピードとメモリのトレードオフを行ういくつかのJOINアルゴリズムをサポートしています:
- パラレルハッシュJOIN(デフォルト): メモリに収まる小中規模の右側テーブルに対して高速です。
- 直接JOIN: 辞書(またはキーと値の特性を持つ他のテーブルエンジン)を使用する場合に理想的で、
INNER
またはLEFT ANY JOIN
のための最速の方法であり、ハッシュテーブルを構築する必要がありません。 - フルソートマージJOIN: 両方のテーブルがJOINキーでソートされている場合に効率的です。
- パーシャルマージJOIN: メモリを最小限に抑えますが、遅くなります - 大きなテーブルを限られたメモリで結合するのに最適です。
- グレースハッシュJOIN: 柔軟でメモリチューン可能で、大規模データセットにおけるパフォーマンス特性の調整に適しています。

各アルゴリズムには、JOINタイプに対する異なるサポートがあります。各アルゴリズムのサポートされているJOINタイプの完全なリストはこちらで確認できます。
ClickHouseに最適なアルゴリズムを選ばせるには、join_algorithm = 'auto'
(デフォルト)の設定を使用するか、ワークロードに応じて明示的に制御します。パフォーマンスまたはメモリオーバーヘッドを最適化するためにJOINアルゴリズムを選択する必要がある場合は、こちらのガイドをお勧めします。
最適なパフォーマンスを得るためには:
- 高パフォーマンスのワークロードではJOINを最小限に抑えます。
- クエリごとに3~4つ以上のJOINを避けます。
- 実データで異なるアルゴリズムをベンチマークします - パフォーマンスはJOINキーの分布とデータサイズに基づいて変動します。
JOIN最適化戦略、JOINアルゴリズム、およびそのチューニング方法については、ClickHouseのドキュメントおよびこのブログシリーズを参照してください。