メインコンテンツまでスキップ
メインコンテンツまでスキップ

辞書

ClickHouse のディクショナリーは、さまざまな 内部および外部ソース からのデータのインメモリ キーと値 表現を提供し、超低遅延のルックアップクエリを最適化します。

ディクショナリーは以下の用途に役立ちます:

  • 特に JOIN と併用したときのクエリパフォーマンスを向上させる
  • データの取り込みプロセスを遅延させずに、取り込まれたデータをその場でリッチ化する
ClickHouseにおけるディクショナリーの使用ケース

Speeding up joins using a Dictionary

ディクショナリーは、特定のタイプの JOIN を高速化するために使用できます: LEFT ANY タイプ で、結合キーは基となるキーと値のストレージのキー属性と一致する必要があります。

LEFT ANY JOINでのディクショナリーの使用

この場合、ClickHouse はディクショナリーを活用して Direct Join を実行できます。これはClickHouseの最速の結合アルゴリズムであり、右側のテーブルが低遅延のキーと値のリクエストをサポートするテーブルエンジンを使用している場合に適用されます。ClickHouse には、この機能を提供する3つのテーブルエンジンがあります:Join(基本的に事前計算されたハッシュテーブル)、EmbeddedRocksDB、および Dictionary です。ここではディクショナリーに基づくアプローチを説明しますが、メカニズムはすべての3つのエンジンで同じです。

直接結合アルゴリズムでは、右側のテーブルがディクショナリーにバックアップされている必要があり、そのテーブルから結合するデータがすでにメモリ内に低遅延のキーと値のデータ構造の形で存在している必要があります。

Example

Stack Overflow データセットを使用して、次の質問に答えます: Hacker News の SQL に関する最も物議を醸す投稿は何ですか?

物議を醸すとは、投稿のアップおよびダウン投票の数が類似している場合と定義します。私たちはこの絶対的な差を計算し、0 に近い値がより物議を醸すことを意味します。投稿は少なくとも10のアップとダウンの投票が必要だと仮定します - 投票されない投稿はあまり物議を醸しません。

データが正規化されている場合、このクエリは現在 posts テーブルと votes テーブルを使用して JOIN を必要とします:

右側の JOIN で小さなデータセットを使用する:このクエリは、必要以上に冗長に見えるかもしれません。PostId でのフィルタリングが外側のクエリとサブクエリの両方で発生します。これは、クエリの応答時間を迅速にするためのパフォーマンス最適化です。最適なパフォーマンスを得るためには、常に JOIN の右側が小さいセットで、可能な限り小さいことを確認してください。JOIN のパフォーマンスを最適化し、利用可能なアルゴリズムを理解するためのヒントについては、この一連のブログ記事をお勧めします。

このクエリは迅速ですが、良好なパフォーマンスを達成するためには、JOIN を慎重に記述する必要があります。理想的には、UpVoteDownVote のカウントを計算するために、SQL を含む投稿をフィルタリングするだけで済むはずです。

Applying a dictionary

これらの概念を示すために、私たちは投票データにディクショナリーを使用します。ディクショナリーは通常メモリ内に保持されますが、ssd_cache が例外ですので、ユーザーはデータのサイズに注意する必要があります。votes テーブルのサイズを確認します:

データはディクショナリー内で非圧縮で保存されるため、すべてのカラム(私たちはそうしませんが)をディクショナリーに保存する場合、少なくとも4GBのメモリが必要です。ディクショナリーはクラスタ全体にレプリケートされるため、このメモリ量は 各ノードごと に確保する必要があります。

以下の例では、ディクショナリーのデータは ClickHouse テーブルから派生しています。これはディクショナリーの最も一般的なソースですが、ファイル、http、Postgres などのデータベースを含む 多くのソース がサポートされています。例示するように、ディクショナリーは自動的に更新され、小さいデータセットが頻繁に変更される場合に直接結合に利用できる理想的な方法を提供します。

私たちのディクショナリーは、ルックアップを実行するための主キーが必要です。これは、トランザクショナルデータベースの主キーと概念的に同じで、ユニークである必要があります。上記のクエリは、結合キー - PostId のルックアップを必要とします。ディクショナリーは、votes テーブルからの各 PostId に対するアップ票とダウン票の合計で構成される必要があります。このディクショナリーのデータを取得するためのクエリは次の通りです:

ディクショナリーを作成するには、次の DDL が必要です - 上記のクエリを使用しています:

セルフマネージド OSS では、上記のコマンドはすべてのノードで実行する必要があります。ClickHouse Cloud では、ディクショナリーがすべてのノードに自動的にレプリケートされます。上記は、64GBのRAMを搭載したClickHouse Cloudノードで実行され、36秒でロードされました。

ディクショナリーによって消費されるメモリを確認します:

特定の PostId のアップ票とダウン票を取得するためには、シンプルな dictGet 関数を使用できます。以下に、投稿 11227902 の値を取得します:

このクエリは非常にシンプルであるだけでなく、実行速度も2倍以上向上しています!さらに最適化するためには、10票以上のアップ票とダウン票を持つ投稿のみをディクショナリーにロードし、予め計算された物議の値を保存することが可能です。

Query time enrichment

ディクショナリーは、クエリ時に値をルックアップするために使用できます。これらの値は結果に返されるか、集計に使用されます。たとえば、ユーザーIDをその場所にマッピングするディクショナリーを作成しましょう:

このディクショナリーを使用して、投稿結果をリッチ化できます:

上記の結合の例と同様に、同じディクショナリーを使用して、投稿の多くがどこから来ているのかを効率的に特定できます:

Index time enrichment

上の例では、クエリ時にディクショナリーを使用して結合を削除しました。ディクショナリーは、挿入時に行をリッチ化するためにも使用できます。これは通常、強化値が変更せず、外部ソースに存在していてディクショナリーをポピュレートするために使用できる場合に適しています。この場合、挿入時に行をリッチ化することで、ディクショナリーへのクエリ時のルックアップを回避します。

Stack Overflow のユーザーの Location が変更されないと仮定します(実際には変更されます) - 特に users テーブルの Location カラムです。投稿テーブルを場所別に分析したいとしましょう。このテーブルには UserId が含まれています。

ディクショナリーは、ユーザーIDを場所にマッピングするもので、users テーブルによってバックアップされています:

Id < 0 のユーザーは除外します。これにより、Hashed ディクショナリータイプを使用できます。Id < 0 のユーザーはシステムユーザーです。

このディクショナリーを利用して挿入時に投稿テーブルを強化するには、スキーマを変更する必要があります:

上記の例では、LocationMATERIALIZED カラムとして宣言されています。これは、値が INSERT クエリの一部として提供され、常に計算されることを意味します。

ClickHouse は、提供されない場合に値を挿入または計算できる DEFAULT カラム もサポートしています。

テーブルをポピュレートするには、S3 から通常の INSERT INTO SELECT を使用できます:

これで、ほとんどの投稿がどこから来ているのかを示す場所の名前を取得できます:

Advanced Dictionary Topics

Choosing the Dictionary LAYOUT

LAYOUT 句は、ディクショナリーの内部データ構造を制御します。いくつかのオプションが存在し、ここ に文書化されています。正しいレイアウトを選択するためのいくつかのヒントは ここ にあります。

Refreshing dictionaries

ディクショナリーに LIFETIMEMIN 600 MAX 900 と指定しています。LIFETIME はディクショナリーの更新間隔であり、ここでの値は600秒から900秒の間のランダムな間隔での定期的なリロードを引き起こします。このランダムな間隔は、大量のサーバーで更新する際にディクショナリーソースへの負荷を分散させるために必要です。更新中、ディクショナリーの古いバージョンはまだクエリでき、初回のロードのみがクエリをブロックします。(LIFETIME(0))を設定すると、ディクショナリーが更新されないことに注意してください。 ディクショナリーは SYSTEM RELOAD DICTIONARY コマンドを使用して強制的にリロードできます。

ClickHouse や Postgres などのデータベースソースでは、ディクショナリーを更新するためのクエリを設定して、定期的な間隔で更新されるのではなく、実際に変更があった場合のみ更新することができます。これについての詳細は ここ にあります。

Other dictionary types

ClickHouse は、階層型ポリゴン、および 正規表現 ディクショナリーもサポートしています。

More reading