なぜ BigQuery ではなく ClickHouse Cloud を使うのか?
BigQuery から ClickHouse Cloud へのデータの読み込み
データセット
post、vote、user、comment、badge が含まれます。このデータの BigQuery スキーマを以下に示します。
このデータセットを BigQuery インスタンスに取り込んで移行手順をテストしたい方向けに、これらのテーブルのデータを Parquet フォーマットで GCS バケットに用意しています。さらに、BigQuery でテーブルを作成してロードするための DDL コマンドは こちら で確認できます。
データの移行
- 初回の一括ロードと定期更新 - 初期データセットを移行したうえで、一定間隔 (例: 毎日) で定期的に更新する必要があります。ここでの更新は、比較に使用できるカラム (例: 日付) で特定した変更済みの行を再送することで処理します。削除については、データセット全体を定期的に完全再ロードすることで対応します。
- リアルタイムレプリケーションまたは CDC - 初期データセットを移行する必要があります。このデータセットへの変更は、数秒程度の遅延のみが許容される、ほぼリアルタイムで ClickHouse に反映されなければなりません。これは実質的に 変更データキャプチャ (CDC) プロセス であり、BigQuery のテーブルを ClickHouse と同期する必要があります。つまり、BigQuery テーブルに対する挿入、更新、削除を、ClickHouse 内の対応するテーブルに適用する必要があります。
Google Cloud Storage (GCS) 経由の一括ロード
- 7 つのテーブルを GCS にエクスポートします。そのためのコマンドはこちらで確認できます。
-
データを ClickHouse Cloud にインポートします。これには gcs table function を使用できます。DDL とインポート用クエリはこちらで確認できます。なお、ClickHouse Cloud のインスタンスは複数のコンピュートノードで構成されるため、
gcstable function ではなく、s3Cluster table function を使用しています。この関数は GCS バケットでも動作し、ClickHouse Cloud service の全ノードを活用してデータを並列にロードします。
- BigQuery のエクスポート機能は、データの一部のみをエクスポートするためのフィルターをサポートしています。
- BigQuery は Parquet、Avro、JSON、CSV フォーマットと複数の圧縮タイプへのエクスポートをサポートしており、これらはすべて ClickHouse でサポートされています。
- GCS は object life cycle management をサポートしているため、ClickHouse へのエクスポートとインポートが完了したデータを、指定した期間の経過後に削除できます。
- Google では 1 日あたり最大 50TB まで無料で GCS にエクスポートできます。ユーザーが支払うのは GCS ストレージの料金のみです。
- エクスポート時には自動的に複数のファイルが生成され、各ファイルは最大 1GB のテーブルデータに制限されます。これによりインポートを並列化できるため、ClickHouse にとって有利です。
定期クエリによるリアルタイムレプリケーションまたは CDC
スキーマの設計
posts テーブルに注目します。その BigQuery のスキーマを以下に示します。
型の最適化
gcs table function を使って gcs からエクスポート済みのデータを読み込み、簡単な INSERT INTO SELECT でデータを投入できます。なお、ClickHouse Cloud では、gcs 互換の s3Cluster table function を使って、複数ノードにまたがる読み込みを並列化することもできます。
ClickHouseの主キーは何が違うのか?
- ClickHouseがよく使われる規模では、メモリ効率とディスク効率が極めて重要です。データはパーツと呼ばれるchunk単位でClickHouseテーブルに書き込まれ、これらのパーツにはバックグラウンドでマージするためのルールが適用されます。ClickHouseでは、各パーツがそれぞれ独自のプライマリインデックスを持ちます。パーツがマージされると、マージ後のパーツのプライマリインデックスもマージされます。なお、これらの索引は各行ごとに作成されるわけではありません。代わりに、パーツのプライマリインデックスには行のグループごとに1つの索引エントリがあります。この手法はスパース索引付けと呼ばれます。
- ClickHouseでは、パーツ内の行が指定したキー順に並べられた状態でディスクに保存されるため、スパース索引付けが可能になります。スパースプライマリインデックスは、単一の行を直接特定するのではなく (B木ベースの索引のように) 、クエリに一致する可能性のある行グループをすばやく特定できます (索引エントリに対するbinary searchによって) 。見つかった一致候補の行グループは、その後、一致する行を見つけるために並列にClickHouse engineへストリーミングされます。この索引設計により、プライマリインデックスを小さく保てるため (全体がmain memoryに収まります) 、クエリ実行時間を大幅に短縮できます。特に、データ分析のユースケースで一般的な範囲クエリで効果を発揮します。詳しくは、この詳細ガイドを参照することをお勧めします。
テーブル内のすべてのカラムは、そのカラム自体がキーに含まれているかどうかにかかわらず、指定された順序付けキーの値に基づいてソートされます。たとえば、CreationDateをキーとして使用する場合、他のすべてのカラムの値の並びもCreationDateカラムの値の並びに対応します。複数の順序付けキーを指定することもでき、この場合はSELECTクエリのORDER BY句と同じ意味で順序付けされます。
順序付けキーの選択
データモデリング手法
パーティション
PARTITION BY 句でパーティション化を指定します。この句には任意のカラムに対する SQL 式を含めることができ、その結果によって行がどのパーティションに送られるかが決まります。
データパーツは、ディスク上で各パーティションに論理的に関連付けられており、個別にクエリできます。以下の例では、toYear(CreationDate) という式を使って posts テーブルを年ごとにパーティション化しています。行が ClickHouse に挿入されると、この式が各行に対して評価され、その後、行はそのパーティションに属する新しいデータパーツとして、対応するパーティションに振り分けられます。
用途
- データ管理 - ClickHouse では、パーティション化はクエリ最適化の手法というより、主にデータ管理のための機能として捉えるべきです。キーに基づいてデータを論理的に分けることで、各パーティションを個別に操作できます。たとえば、削除が可能です。これにより、時間に応じてパーティション、ひいてはデータのサブセットを ストレージ階層 間で効率よく移動したり、データを期限切れにする/クラスターから効率的に削除する ことができます。以下の例では、2008 年の投稿を削除しています。
- クエリ最適化 - パーティションはクエリ性能の向上に役立つことがありますが、その効果はアクセスパターンに大きく左右されます。クエリの対象が少数のパーティション (理想的には 1 つ) に限られる場合は、性能が向上する可能性があります。通常、これが有効なのは、パーティション化キーが主キーに含まれておらず、そのキーで絞り込みを行う場合に限られます。一方、多数のパーティションをまたぐ必要があるクエリでは、パーティション化しない場合よりも性能が低下することがあります (パーティション化によってパーツ数が増える可能性があるためです) 。また、パーティション化キーがすでに主キーの先頭寄りのエントリに含まれている場合、単一パーティションを対象にする利点はさらに小さくなり、ほとんど、あるいはまったくなくなります。さらに、各パーティション内の値が一意であれば、パーティション化は
GROUP BYクエリの最適化 にも利用できます。ただし一般的には、まず主キーが適切に最適化されていることを確認し、パーティション化をクエリ最適化の手法として検討するのは、アクセスパターンがその日のうちの特定の予測可能な部分集合に集中する例外的なケース、たとえば日単位でパーティション化し、ほとんどのクエリが直近 1 日を対象とするような場合に限るべきです。
推奨事項
ORDER BY 式の先頭カラムにしてください。
内部的には、ClickHouse は挿入されたデータに対してパーツを作成します。データの挿入が増えるにつれて、パーツ数も増加します。パーツ数が過度に増えるのを防ぐため (読み込むファイル数が増え、クエリ性能が低下するため) 、パーツはバックグラウンドで非同期にマージされます。パーツ数が事前設定された上限を超えると、ClickHouse は INSERT 時に”パーツが多すぎる” エラーとして例外をスローします。これは通常の運用では発生しないはずで、ClickHouse の設定が不適切であるか、たとえば小さな insert を大量に行うなど、誤った使い方をした場合にのみ発生します。パーツは各パーティションごとに独立して作成されるため、パーティション数が増えるとパーツ数も増加します。つまり、パーツ数はパーティション数に応じて増えます。したがって、高カーディナリティのパーティション化キーはこのエラーの原因になり得るため、避けるべきです。
materialized view と プロジェクション の比較
ORDER BY 句を指定できます。
ClickHouse のデータモデリングでは、ClickHouse で materialized view を使用して
集計を事前計算し、行を変換し、さまざまなアクセスパターンに合わせてクエリを最適化する方法を説明しています。
後者については、例を紹介しました。そこでは、
materialized view が、挿入を受け取る元のテーブルとは異なる順序付けキーを持つ
ターゲットテーブルに行を送ります。
たとえば、次のクエリを考えてみましょう。
UserId が
順序付けキーではないため、9,000万行すべてをスキャンする必要があります (とはいえ高速に処理されます) 。
以前は、PostId のルックアップとして機能する materialized view
を使ってこの問題を解決していました。同じ問題はプロジェクションでも解決できます。
以下のコマンドでは、ORDER BY user_id を持つプロジェクションを追加します。
ALTER コマンドで作成した場合、MATERIALIZE PROJECTION コマンドを発行すると、
作成処理は非同期で実行されます。この操作の進行状況は、is_done=1 になるまで待ちながら、
次のクエリで確認できます。
EXPLAIN コマンドを使うと、このクエリの実行にプロジェクションが使用されたことも確認できます。
プロジェクションを使用する場面
- データを完全に並べ替える必要がある場合。理論上、プロジェクション内の式では
GROUP BY,も使用できますが、集計を維持する用途では materialized view のほうが効果的です。また、クエリオプティマイザは、単純な並べ替え、つまりSELECT * ORDER BY xを使用するプロジェクションのほうを利用しやすい傾向があります。この式では、保存領域を減らすためにカラムの一部だけを選択することもできます。 - 保存領域の増加と、データを 2 回書き込むオーバーヘッドをユーザーが許容できる場合。挿入速度への影響をテストし、ストレージオーバーヘッドを評価してください。
BigQuery クエリを ClickHouse で書き換える
ClickHouse
集約関数
argMax 関数 を使って、各年で最も閲覧数の多い質問を求める例を示します。
BigQuery
ClickHouse
条件式と配列
HAVING 句と SELECT 句で別名を再利用できることによって、簡潔に記述できている点に注目してください。
BigQuery
ClickHouse