このトピックでは、ApsaraDB for ClickHouse でテーブルのパーティションキーを正しく選択し、テーブル設計シナリオにおけるパフォーマンスの最適化とデータ管理効率の向上を図る方法について説明します。
パーティションキー
パーティション機能は、指定されたキーに基づいてデータを論理的なセグメントに整理します。データは、パーティションキーに従って個別のフラグメント (パーツ) に分割されます。
ApsaraDB for ClickHouse Enterprise Edition では、パーティションキーを使用しないテーブルに INSERT 文を実行して複数の行を挿入すると、すべてのデータが新しいデータパーツに書き込まれます。しかし、テーブルがパーティションキーを使用する場合、システムは次の操作を実行します:
挿入する行のパーティションキーの値を確認します。
一意のパーティションキー値ごとにストレージ内にデータパーツを作成します。
パーティションキーの値に基づいて、対応するデータパーツに行を割り当てます。
パーティションキーのないテーブル
| パーティションキーのあるテーブル
|
コア原則
ApsaraDB for ClickHouse Enterprise Edition のデータを格納する Object Storage Service バケットに送信される書き込みリクエストの数を減らすには、パーティションキーは低カーディナリティ (一意のパーティション値の数が少ない) と管理しやすいフィールド (時間など) を優先する必要があります。プライマリキーは、一般的にフィルターされるフィールドをカバーし、論理的な順序である必要があります。高カーディナリティのフィールド、過度に細かいパーティション分割、および無関係なプライマリキーを避けて、ClickHouse の高性能と容易な管理の利点を活用してください。
パーティション分割は データ管理ツール
パーティション分割は、主に効率的なデータ期限切れ、階層型ストレージ、バッチ削除、およびその他の管理タスクに使用され、主要なクエリ最適化ツールとしてではありません。詳細については、「Choosing a Partitioning Key」をご参照ください。
パーティションキーとして低カーディナリティのフィールドを選択する
パーティションの数は 100 から 1,000 の間に保つことをお勧めします。高カーディナリティのフィールド (user_id やデバイス番号など、多くの異なる値を持つフィールド) をパーティションキーとして使用することは避けてください。そうしないと、パーツの数が指数関数的に増加し、パフォーマンスに影響を与えたり、「パーツが多すぎます」というエラーが発生したりする可能性があります。
一般的なパーティション分割方法は時間ベースのパーティション分割です
toYYYYMM(date)、toStartOfMonth(date)、または toDate(date) などの関数を使用して、月や日などの時間ディメンションでパーティション分割します。これにより、データのライフサイクル管理とホットデータおよびコールドデータの階層型ストレージが容易になります。詳細については、「Custom Partitioning Key」をご参照ください。
パーティションキーは、データのライフサイクル、アーカイブ、クリーンアップ、およびその他の管理要件と密接に連携させる必要があります
ビジネスの観点からバッチで管理しやすいディメンションを優先します。詳細については、「Applications of partitioning」をご参照ください。
テーブル設計の推奨事項
時間ベースのパーティション分割を優先する
ログ、時系列、およびモニタリングのシナリオでは、月または日でパーティション分割することをお勧めします。たとえば、ログテーブルを月でパーティション分割し、各月のデータを個別のパーティションに格納すると、次の利点があります:
効率的な データ管理: パーティションごとにデータをバッチで削除、アーカイブ、または移動できます。たとえば、
ALTER TABLE DELETEを使用して期限切れのデータを削除する一般的なチケットシナリオでは、月次または日次パーティション分割を使用すると、テーブル全体をスキャンすることなくDROP PARTITIONを使用して対応するパーティションを削除するだけで済み、効率が大幅に向上します。簡単な データライフサイクル管理 (TTL) の実装: TTL ポリシーと組み合わせることで、期限切れのパーティションを自動的にクリーンアップでき、運用とメンテナンスが簡素化されます。
パーティションプルーニングによる クエリ効率の向上: クエリで時間によってフィルターする場合、ClickHouse は関連するパーティションのみをスキャンし、無関係なパーティションをスキップするため、I/O が大幅に削減され、クエリ速度が向上します。
パーティション分割に高カーディナリティのフィールドを避ける
ユーザー ID、注文番号、デバイス番号などです。たとえば、テーブルがパーティションキーとして user_id を使用する場合、これは高カーディナリティのフィールド (各ユーザーが一意に表現され、高カーディナリティを示します) であり、非常に多くのパーティションが生成され、次のような欠点があります:
パーティション爆発: 一意のユーザー ID ごとにパーティションが生成され、推奨範囲の 100〜1,000 をはるかに超える非常に多数のパーティションが作成され、メタデータ管理とファイルシステムに大きな負荷がかかります。
バックグラウンドマージの失敗: ClickHouse は同じパーティション内のパーツのみをマージします。パーティションが多すぎるとマージ操作が妨げられ、クエリと書き込みのパフォーマンスに影響を与える多くの小さなパーツが生成されます。
クエリパフォーマンスの低下: パーティションが多すぎると、クエリ中に大量のパーティションメタデータをスキャンする必要があり、クエリ効率が低下します。
インスタンスリソースの枯渇: パーティションパーツが多すぎると、大量のメモリとファイルハンドルが消費され、ClickHouse の起動が遅くなったり、失敗したりする可能性があります。
過度に細かいパーティション分割を避ける
データ量が非常に多く、特定の要件がない限り、時間、分、または秒によるパーティション分割などです。たとえば、テーブルが toYYYYMMDDhhmm(event_time) を使用して分単位でパーティション分割する場合、1 日あたり 1,440 個、1 年あたり 500,000 個以上のパーティションが生成され、次のような欠点があります:
パーティションが多すぎる: 過度に細かいパーティション分割は、推奨範囲の 100〜1,000 をはるかに超える数のパーティションを生成し、メタデータとファイルシステムの管理負担を大幅に増大させます。
典型的なエラー:
DB::Exception: Too many parts (N). Merges are processing significantly slower than inserts.バックグラウンドマージの失敗: ClickHouse は同じパーティション内のパーツのみをマージします。パーティションが多すぎるとマージ操作が妨げられ、クエリと書き込みのパフォーマンスに影響を与える多くの小さなパーツが生成されます。
クエリと書き込みのパフォーマンス低下: パーティションが多すぎると、クエリ中に大量のパーティションメタデータをスキャンする必要があり、クエリ効率が低下します。また、パーツ数が多すぎるため、書き込みも遅くなります。
パーティションキーは元のフィールドまたは単純な式である必要があります
ClickHouse がパーティションプルーニングを利用できるように、複雑な関数は避けてください。
プライマリキーと組み合わせてパーティションキーを設計する
プライマリキーは一般的にクエリされるフィルターフィールドをカバーし、パーティションキーはデータ管理の目的を果たします。
たとえば、典型的なビジネス要件が時間範囲とサービス名で頻繁にクエリし、定期的に期限切れのデータをクリーンアップするログテーブルの場合、設計は次のようになります:
パーティションキー: toYYYYMM(event_time)、月ごとに 1 つのパーティション。これにより、月ごとのバッチ削除、アーカイブ、ホットデータとコールドデータの階層型ストレージ、およびその他のデータ管理操作が容易になります。
プライマリキー: (service_name, event_time)、
WHERE service_name = 'A' AND event_time BETWEEN ...のような一般的なクエリの場合、プライマリキーインデックスを完全に利用してデータプルーニングを行い、クエリを高速化できます。
テーブル設計の例
CREATE TABLE logs
(
event_time DateTime,
service_name String,
log_level String,
message String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time) -- データ管理を容易にするために月でパーティション分割
ORDER BY (service_name, event_time) -- プライマリキーは一般的にフィルターされるフィールドをカバー推奨されないパーティションキーの選択
パーティションキーとして user_id (高カーディナリティ): ユーザーごとに 1 つのパーティションが作成され、パーティションが多すぎ、マージが失敗し、パフォーマンスが極端に低下します。
パーティションキーとして device_id (高カーディナリティ): 上記と同様、「パーツが多すぎます」エラーが発生し、管理不能なパーティションが作成されます。
パーティションキーとして order_id (高カーディナリティ): 注文ごとに 1 つのパーティション、極端な断片化。
パーティションキーとして name (高カーディナリティ文字列): 制御不能な数のパーティション、管理が困難。
パーティションキーとして toHour(event_time) (細かすぎる): 1 日あたり 24 パーティション、時間とともに非常に多数のパーティションが作成され、マージが失敗します。
パーティションキーとして toMinute(event_time) (極端に細かい): パーティション爆発、パフォーマンスに深刻な影響を与えます。
非論理的な順序の高カーディナリティフィールドをプライマリキーとして使用: たとえば、ORDER BY (user_id, event_time) ですが、クエリは一般的に event_time で行われるため、プライマリキーインデックスの利用率が低くなります。
フィールドが多すぎるプライマリキー: たとえば、ORDER BY (a, b, c, d, e, f, g, h, i, j) の場合、プライマリキーインデックスのサイズが大きくなり、メモリ消費量が高くなります。
低カーディナリティフィールドをプライマリキーとして使用: たとえば、ORDER BY (status) で、ステータス値が少数しかない場合、プライマリキーインデックスのプルーニング能力が非常に低くなります。
パーティションキーとプライマリキーが完全に関連しておらず、どちらも一般的なクエリ条件をカバーしていない: たとえば、パーティションキーがリージョン、プライマリキーがタイプであるが、クエリは一般的に event_time で行われるため、パーティションもプライマリキーもクエリを高速化しません。

