このページでは、「ソートキー」という用語を「primary key」とほぼ同義で使用しています。厳密には、ClickHouse では両者は異なりますが、このドキュメントでは同じものとして捉えて差し支えありません。ここでいう ソートキー は、テーブルの ORDER BY で指定するカラムを指します。
ClickHouse の主キーは、Postgres などの OLTP データベースにおける同様の用語に慣れている方の想像とは大きく異なる点に注意してください。
ClickHouse で効果的な主キーを選ぶことは、クエリパフォーマンスとストレージ効率の両方にとって非常に重要です。ClickHouse はデータを複数のパーツに分割して管理し、各パーツはそれぞれ独自のスパースプライマリ索引を持ちます。この索引により、スキャンするデータ量を減らせるため、クエリを大幅に高速化できます。さらに、主キーはデータがディスク上で物理的にどの順序で配置されるかを決めるため、圧縮効率にも直接影響します。最適な順序で配置されたデータはより効率よく圧縮され、I/O が減ることでパフォーマンスがさらに向上します。
- ソートキー を選ぶ際は、クエリのフィルタ (つまり
WHERE句) で頻繁に使用されるカラム、特に大量の行を除外できるカラムを優先してください。 - テーブル内の他のデータとの相関が高いカラムも有効です。連続した形で格納されることで、
GROUP BYやORDER BYの処理時に圧縮率とメモリ効率が向上するためです。
ソートキー を選ぶ際の助けとなる簡単なルールがいくつかあります。以下の条件は互いに相反する場合もあるため、順に検討してください。このプロセスで複数のキー候補を特定できますが、通常は 4~5 個で十分です:
重要ソートキー はテーブル作成時に定義する必要があり、後から追加することはできません。一方で、プロジェクションと呼ばれる機能を使えば、データ挿入後 (または前) に追加の並び順をテーブルに加えることができます。ただし、その場合はデータが重複する点に注意してください。詳細はこちらを参照してください。
例
posts_unordered テーブルについて見てみましょう。このテーブルには、Stack Overflow の各投稿に対応する行が 1 つずつ含まれます。
このテーブルには主キーがありません。これは ORDER BY tuple() で示されています。
EXPLAIN indexes=1 を使うと、索引がないためにテーブル全体のスキャンが発生していることを確認できます。
posts_ordered が、ORDER BY に (PostTypeId, toDate(CreationDate)) を指定して定義されていると仮定します。つまり、
PostTypeId のカーディナリティは 8 で、ソートキーの最初のエントリとして理にかなった選択です。日付粒度でのフィルタリングで十分である可能性が高く (datetime フィルターにも引き続き効果があります) 、キーの 2 番目の部分には toDate(CreationDate) を使用します。これにより、日付は 16 bits で表現できるため、より小さな索引を作成でき、フィルタリングも高速化されます。
次のアニメーションは、Stack Overflow の Posts テーブルに対して最適化されたスパースプライマリインデックスがどのように作成されるかを示しています。個々の行に索引を作成するのではなく、この索引は行のブロックを対象とします。
同じクエリを、このソートキーを持つテーブルに対して繰り返すと、次のようになります。
EXPLAIN indexes=1 で確認できます。
テーブル内のすべてのカラムは、キー自体に含まれているかどうかにかかわらず、指定されたソートキーの値に基づいてソートされます。たとえば、
CreationDate をキーとして使用した場合、他のすべてのカラムの値の並び順は CreationDate カラムの値の並び順に対応します。複数のソートキーを指定することもでき、その場合は SELECT クエリの ORDER BY 句と同じ意味で並べ替えられます。