これは、PostgreSQL から ClickHouse への移行ガイドの第3部です。実践的な例を通じて、PostgreSQL から移行する場合に ClickHouse でどのようにデータをモデリングするかを説明します。Postgres から移行するユーザーには、ClickHouse でのデータモデリングに関するガイドを読むことをお勧めします。このガイドでは同じ Stack Overflow データセットを使用し、ClickHouse の機能を活用した複数のアプローチを紹介しています。
OLTPデータベースに慣れたユーザーは、ClickHouseでもそれに相当する概念を探しがちです。ClickHouseが
PRIMARY KEY 構文をサポートしているのを見ると、元のOLTPデータベースと同じキーでテーブルのスキーマを定義したくなるかもしれません。しかし、これは適切ではありません。
ClickHouseの主キーは何が違うのか?
- Postgresの主キーは、定義上、各行で一意です。B-tree structures を利用することで、このキーによる単一行の効率的なルックアップが可能になります。ClickHouseでも単一の行の値をルックアップしやすく最適化することは可能ですが、分析ワークロードでは通常、多数の行に対して少数のカラムを読み取ることが求められます。多くの場合、filterでは、aggregationの対象となる行のサブセットを特定する必要があります。
- ClickHouseがよく使われるような大規模環境では、メモリ効率とディスク効率が極めて重要です。データは、パーツと呼ばれるchunk単位でClickHouseのテーブルに書き込まれ、これらのパーツをバックグラウンドでmergeするルールが適用されます。ClickHouseでは、各パーツがそれぞれ独自のプライマリインデックスを持ちます。パーツがmergeされると、merge後のパーツのプライマリインデックスもmergeされます。Postgresとは異なり、これらの索引は各行ごとに構築されるわけではありません。代わりに、パーツのプライマリインデックスには、行のグループごとに1つの索引エントリがあります。この手法はスパースインデックスと呼ばれます。
- スパースインデックスが成り立つのは、ClickHouseが、指定したキーの順序でパーツ内の行をディスクに格納するためです。スパースプライマリインデックスは、単一行を直接特定するのではなく (B-Treeベースの索引のように) 、クエリに一致する可能性のある行グループをすばやく特定できます (索引エントリに対するbinary searchによって) 。該当する可能性のある行グループが見つかると、それらは並列にClickHouse engineへstreamされ、一致する行が絞り込まれます。この索引設計により、プライマリインデックスを小さく保てるため (main memoryに完全に収まります) 、特にデータ分析で典型的な範囲クエリにおいて、クエリ実行時間を大幅に短縮できます。
テーブル内のすべてのカラムは、そのカラム自体がキーに含まれているかどうかにかかわらず、指定された並び順キーの値に基づいてソートされます。たとえば、CreationDateをキーとして使用した場合、他のすべてのカラムの値の並び順はCreationDateカラムの値の並び順に対応します。並び順キーは複数指定することもでき、その場合の並び順はSELECTクエリのORDER BYclause と同じ意味になります。
並び順キーの選択
パーティション
PARTITION BY句で指定します。この句には任意のカラムに対するSQL式を含めることができ、その結果によって行がどのパーティションに送られるかが決まります。
データパーツは、ディスク上で各パーティションに論理的に関連付けられており、個別にクエリできます。以下の例では、toYear(CreationDate)式を使ってpostsテーブルを年ごとにパーティション化しています。行がClickHouseに挿入されると、この式が各行に対して評価され、対応するパーティションが存在すればそこに振り分けられます (その年の最初の行であれば、パーティションが作成されます) 。
パーティションの用途
- データ管理 - ClickHouse では、パーティション化はクエリ最適化の手法ではなく、基本的にはデータ管理機能として捉えるべきです。キーに基づいてデータを論理的に分離することで、各パーティションを個別に操作できます。たとえば削除です。これにより、時間に基づいて ストレージ階層 間でパーティション、ひいてはそのサブセットを効率的に移動したり、データを期限切れにする/クラスターから効率的に削除する ことができます。以下の例では、2008 年の投稿を削除します。
- クエリ最適化 - パーティションはクエリ性能の向上に役立つことがありますが、その効果はアクセスパターンに大きく左右されます。クエリが少数のパーティション (理想的には 1 つ) だけを対象とする場合は、性能が向上する可能性があります。通常、これが有効なのは、パーティションキーが主キーに含まれておらず、そのキーでフィルタしている場合に限られます。一方で、多数のパーティションをまたぐ必要があるクエリは、パーティション化しない場合よりも性能が悪化することがあります (パーティション化によってパーツ数が増える可能性があるためです) 。また、パーティションキーがすでに主キーの先頭付近に含まれている場合は、単一パーティションを対象にする利点はさらに薄れ、ほとんど、あるいはまったくなくなります。さらに、各パーティション内の値が一意であれば、パーティション化は GROUP BY クエリの最適化 にも利用できます。ただし一般的には、まず主キーが適切に最適化されていることを確認し、パーティション化をクエリ最適化の手法として検討するのは、アクセスパターンがその日のうちの予測可能な特定の部分集合に集中する例外的なケース、たとえば日単位でパーティション化し、ほとんどのクエリが直近 1 日を対象とするような場合に限るべきです。
パーティションに関する推奨事項
ClickHouse は内部的に、挿入されたデータに対してパーツを作成します。データがさらに挿入されると、パーツの数は増えていきます。パーツ数が過剰に増えてクエリ性能が低下すること (読み取るファイル数が増えるため) を防ぐため、パーツはバックグラウンドで非同期にマージされます。パーツ数があらかじめ設定された制限を超えると、ClickHouse は INSERT 時に例外をスローし、「パーツが多すぎる」エラーになります。これは通常の運用では発生せず、ClickHouse の設定が不適切であるか、たとえば小さな insert を大量に行うなど、誤った使い方をした場合にのみ起こります。
パーツはパーティションごとに独立して作成されるため、パーティション数を増やすとパーツ数も増加します。つまり、パーツ数はパーティション数に応じて増えます。そのため、高カーディナリティのパーティション化キーはこのエラーの原因になり得るため、避けるべきです。
materialized view と プロジェクション の比較
ORDER BY 句を指定できます。
ClickHouse の データモデリングドキュメント では、ClickHouse で materialized view を使って集計を事前計算し、行を変換し、さまざまなアクセスパターンに合わせてクエリを最適化する方法を説明しています。
このうち後者については、例 として、materialized view が、挿入を受け取る元のテーブルとは異なる並び順キーを持つターゲットテーブルに行を送るケースを紹介しました。
たとえば、次のクエリを考えてみましょう。
UserId がソートキーではないため、9,000万行すべてを (とはいえ高速に) スキャンする必要があります。
以前は、PostId のルックアップとして機能する materialized view を使ってこれを解決していました。同じ問題は
PROJECTION でも解決できます。以下のコマンドは
ORDER BY user_id 用の PROJECTION を追加します。
ALTERで作成した場合、MATERIALIZE PROJECTIONコマンドを発行すると、作成は非同期で実行されます。is_done=1になるまで待ちながら、次のクエリでこの操作の進捗を確認できます。
EXPLAIN コマンドを使うと、このクエリの実行にプロジェクションが使われたことも確認できます:
PROJECTIONを使用するタイミング
- データを完全に並べ替える必要がある場合。理論上、PROJECTION内の式では
GROUP BY,を使用できますが、集計の維持には materialized view のほうが効果的です。 また、クエリオプティマイザは、SELECT * ORDER BY xのような単純な並べ替えを使うPROJECTIONを 利用しやすい傾向があります。ストレージ使用量を減らすために、この式ではカラムの一部だけを選択できます。 - ストレージ使用量の増加と、データを 2 回書き込むオーバーヘッドを 許容できる場合。挿入速度への影響をテストし、 ストレージオーバーヘッドを評価してください。
バージョン 25.5 以降、ClickHouse はPROJECTION内の仮想カラム
_part_offset をサポートしています。
これにより、より少ない容量でPROJECTIONを保存できるようになります。詳細は “Projections” を参照してください。