Dictionary と JOIN の使い分け
WHERE フィルタで大半の行が破棄される場合でもです。最近のバージョン (24.12+) では、多くのケースで JOIN より前にフィルタが適用されるようになりましたが、それでもオーバーヘッドを常に解消できるとは限りません。一方、Dictionary では dictGet をインラインで呼び出すため、ルックアップが実行されるのは、フィルタを通過した行だけです。
ただし、dictGet が常に最適な選択肢とは限りません。テーブル内のかなり多くの行に対して dictGet を呼び出す必要がある場合、たとえば dictGet('dict', 'elevation', id) > 1800 のような WHERE 条件では、通常のカラムとネイティブな索引を使うほうが適していることがあります。通常のカラムであれば、ClickHouse は PREWHERE を使って granule をスキップできますが、dictGet は索引の支援なしに行ごとに評価されます。
経験則としては、次のとおりです。
- ルックアップキーがすでに利用可能な小さなディメンションテーブルに対する JOIN を置き換えるには、Dictionary を使用します。
- 多くの行にわたってルックアップした値でフィルタする場合は、通常のカラムと索引を使用します。
レイアウトの選択
LAYOUT 句は、Dictionary の内部データ構造を制御します。使用可能なすべてのレイアウトは、レイアウトのリファレンス に記載されています。
レイアウトを選ぶ際は、次のガイドラインを参考にしてください。
flat— 最も高速なレイアウトです (単純な配列オフセットによるルックアップ) 。ただし、キーはUInt64である必要があり、デフォルトでは 500,000 (max_array_size) までに制限されます。小規模から中規模のテーブルで、単調増加する整数キーに最適です。キー分布がスパースな場合 (たとえばキー値が 1 と 500,000 のような場合) 、配列は最大キーに合わせて確保されるため、メモリを無駄にします。500k の上限に達しているなら、hashed_arrayへ切り替えるべきサインです。hashed_array— ほとんどのユースケースで推奨されるデフォルトです。属性を配列に格納し、ハッシュテーブルでキーを配列インデックスにマッピングします。hashedとほぼ同等の速度で、特に属性が多い場合はよりメモリ効率に優れます。hashed— Dictionary 全体をハッシュテーブルに格納します。属性がごく少ない場合はhashed_arrayより高速になることがありますが、属性数が増えるにつれてメモリ消費も大きくなります。complex_key_hashed/complex_key_hashed_array— キーをUInt64に CAST できない場合 (たとえばStringキー) に使用します。性能面でのトレードオフは、complex でない対応レイアウトと同じです。sparse_hashed—hashedよりメモリ使用量を抑えられる一方で、CPU コストは増えます。最適な選択になることはまれで、効率的なのは属性が 1 つしかない場合に限られます。ほとんどの場合、hashed_arrayのほうが適しています。cache/ssd_cache— 頻繁にアクセスされるキーだけをキャッシュします。データセット全体がメモリに収まらない場合に有用ですが、cache ミス時にはルックアップがソースに到達することがあります。レイテンシに敏感なワークロードには推奨されません。direct— インメモリストレージを使わず、すべてのルックアップでソースに対してクエリを実行します。データの更新頻度が高すぎてキャッシュできない場合や、Dictionary が大きすぎてメモリに収まらない場合に使用してください。
Dictionaryの使用状況を監視する
system.dictionaries テーブルを使用して、メモリ使用量と正常性を確認できます。
bytes_allocated— Dictionary が消費しているメモリ量。Dictionary はデータを非圧縮で格納するため、この値は圧縮後のテーブルサイズを大幅に上回ることがあります。hit_rateとfound_rate—cacheレイアウトの有効性を評価する際に役立ちます。last_exception— Dictionary の読み込みや更新に失敗した場合は、ここを確認してください。