リアルタイム分析データウェアハウジングオブザーバビリティAI/MLCloudOSS
前提条件
- A running ClickHouse Cloud service. If you don’t have one yet, complete the Create your first Cloud service quickstart first.
作成するもの
ORDER BY と PARTITION BY を選択して、S3 からデータを直接読み込み、その後 system.parts にクエリを実行して、ClickHouse がディスク上でデータを物理的にどのように配置しているかを確認します。
最後には、MergeTree エンジンがほぼすべての ClickHouse テーブルの基盤である理由と、そのソートやパーティション化の選択がクエリ性能にどのように直接影響するかを理解できるようになります。
MergeTree の仕組みを理解する
SQL を書く前に、MergeTree が従来のデータベーステーブルと何が違うのかを知っておくと役立ちます。MergeTree テーブルにデータを挿入しても、ClickHouse は行を 1 行ずつ書き込むわけではありません。代わりに、data part (ソート済みかつ圧縮された、行の小さなまとまり) を直接ディスクに書き込みます。その後、ClickHouse はそれらのパーツを時間をかけてバックグラウンドでマージしていきます。名前の由来はまさにここにあります: merge + tree。各 data part は、テーブルのORDER BY 式に従ってソートされます。この並び順が 主キーインデックス となり、ClickHouse はクエリ実行時に読み取る必要のない大きなデータブロックをスキップできるようになります (これをデータのプルーニングと呼びます) 。よく使うクエリに対して ORDER BY のカラムの選択性が高いほど、ClickHouse が読み取るデータ量は少なくなります。MergeTree がデータをどのように構成するかは、3 つの句で制御されます。| Clause | 役割 |
|---|---|
ORDER BY | 各パート内のデータを物理的にソートします。主キーを決定します。必須です。 |
PARTITION BY | データを個別のパーティションに分割します。通常は日付範囲ごとです。異なるパーティションのパート同士がマージされることはないため、高速なパーティションプルーニングが可能になります。 |
PRIMARY KEY | 明示的により短いプレフィックスを設定しない限り、デフォルトでは ORDER BY になります。スパースインデックスはこれをもとに構築されます。 |
ソースデータをプレビューする
テーブルを作成する前に、s3テーブル関数を使ってソースファイルの内容を確認します。これにより、まず ClickHouse にデータを書き込まなくても、S3 を直接クエリできます。SQLコンソールで次を実行します。Nullable(String) と推論されている点に注意してください。ClickHouse は生の CSV を読み込んでいるため、実際のデータ型までは判別できません。これは、次のステップでテーブルのスキーマを設計する際に修正します。いくつかの行をプレビューします:id、売買 price、date、物件 type、住所関連のフィールド、地理識別子が含まれます。また、末尾の 2 つのカラム (column15、column16) は空になっていますが、これらは無視してかまいません。これを確認するには、id、price、date、postcode、type、town、county などのカラムを含む行が表示されることを確認してください。MergeTree テーブルを設計して作成する
次に、適切なスキーマを持つ永続テーブルを作成します。以下のカラム型は意図して選ばれています。LowCardinality(String)は、一意の値が限られているカラム (郵便番号、町名、カウンティ名) に使用します。内部的に辞書エンコーディングを使うことで、これらのカラムでのグループ化やフィルタリング時のストレージ使用量を大幅に削減し、パフォーマンスも向上させます。Enum8は、クエリでは人間が読める文字列ラベルを保ったまま、typeカラムとdurationカラムをディスク上で小さな整数としてエンコードします。元の CSV では 1 文字のコードが使われているため、INSERT 時にそれらをマッピングします。PARTITION BY toYYYYMM(date)は、暦月ごとに 1 つのパーティションを作成します。これにより、WHERE句でdateを条件に指定した場合、ClickHouse は月単位でデータを丸ごとスキップできます。ORDER BY (postcode, addr1, addr2)は、物件住所による高速なルックアップ (このデータセットで最も自然なアクセスパターン) を支えるためにデータをソートします。
ENGINE = MergeTree を指定したにもかかわらず、ClickHouse Cloud では SharedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}') でテーブルが作成されていることに注目してください。これは想定どおりの動作です。Cloud は MergeTree を自動的に SharedMergeTree に変換し、レプリケーションと共有ストレージのサポートを追加します。動作とクエリインターフェイスは変わりません。S3 からデータを読み込む
s3() テーブル関数から直接選択して、データセット全体を挿入します。ClickHouse は圧縮ファイルを S3 からストリーミングし、ソート済みのパーツとしてテーブルに書き込みます。T は terraced、F は freehold、Y/N は new-build) 、transform を使って読みやすいラベルに変換し、toUInt32/if で数値カラムをキャストします。id、column15、column16 の各カラムは不要なため除外します。これには、サービスのサイズに応じて1〜2分ほどかかります。完了したら、行数を確認します:system.parts を使ってパーツを確認する
ここで、MergeTree の内部の仕組みが見えてきます。system.parts テーブルには、サービス内のすべての MergeTree テーブルについて、ディスク上のすべてのデータパーツが記録されています。partition-PARTITION BY式から導出されたYYYYMMの値です。月ごとにデータが分けられています。name- パーツ名には、パーティション、ブロック番号の範囲、マージレベルがエンコードされています (例:199501_1_4_2は、パーティション199501、ブロック 1~4、2 回マージ済みであることを意味します) 。marks- インデックスグラニュールの数です。各グラニュールはデフォルトで 8,192 行をカバーし、主キーインデックスはグラニュールごとに 1 つのエントリを保持します。このスパースインデックスがメモリ上に保持されることで、高速なデータスキップが可能になります。bytes_on_disk- ClickHouse はデフォルトで、各パーツをカラムごとに LZ4 で圧縮します。これを生のサイズと比較すると、圧縮率を把握できます。
active = true のフィルタにより、クリーンアップ待ちの古いパーツではなく、現在のマージ済みパーツだけが表示されます。データにクエリを実行し、主キーの挙動を確認する
ここで、実際に分析クエリをいくつか実行してみましょう。まず、記録されている売上の中で最も高額なものを見つけます。price は ORDER BY キーの一部ではないため、ClickHouse はプライマリインデックスを使ってデータを読み飛ばせず、テーブル全体をスキャンする必要があります。次に、郡ごとの平均販売価格を求めます。county は ORDER BY にも PARTITION BY にも含まれていないため、ClickHouse はテーブル全体をスキャンします。次に、集計と ORDER BY を組み合わせたクエリを実行します。データは (postcode, addr1, addr2) でソートされているため、郵便番号のプレフィックスで絞り込むと、ClickHouse はテーブルの大部分をスキップできます。ここでは、SW1A の郵便番号エリアにある物件について、年ごとの平均売却価格を求めます。postcode で絞り込んだ集計では、テーブルの行のごく一部だけが読み取られるはずで、これは主キー索引が機能していることを示しています。これを、より広い範囲をスキャンする前述のクエリと比較してください。この違いから、適切な ORDER BY を選ぶことがなぜ重要なのかがわかります。