テーブルパーティション
ClickHouseのテーブルパーティションとは?
パーティションは、MergeTreeエンジンファミリーのテーブルのデータパーツを組織的かつ論理的な単位にグループ化します。これは、時間範囲、カテゴリー、または他のキー属性など、特定の基準に沿った概念的に意味のあるデータの整理方法です。これらの論理的な単位は、データの管理、クエリ、最適化を容易にします。
パーティションの定義
テーブルを最初に定義する際に、PARTITION BY句を使用してパーティションを有効にできます。この句には、任意のカラムに対するSQL式を含めることができ、結果として行がどのパーティションに属するかを定義します。
これを示すために、PARTITION BY toStartOfMonth(date)
句を追加し、テーブルのデータパーツをプロパティの販売に基づいて月ごとに整理するWhat are table partsの例テーブルを強化します:
このテーブルをクエリすることができます。
ディスク上の構造
行のセットがテーブルに挿入されるたびに、挿入されたすべての行を含む(最低限)1つのデータパートを作成するのではなく、ClickHouseは挿入された行の中で各ユニークパーティションキー値ごとに1つの新しいデータパートを作成します:

ClickHouseサーバーは、上記の図でスケッチされた4行の例挿入から行をそのパーティションキー値toStartOfMonth(date)
に従って最初に分割します。
その後、特定された各パーティションの行は、通常通りに処理されます(① ソート、② カラムへの分割、③ 圧縮、④ ディスクへの書き込み)。
パーティションが有効になっている場合、ClickHouseは自動的に各データパートに対してMinMaxインデックスを作成することに注意してください。これらは、パーティションキー式に使用される各テーブルカラムの最小値と最大値を含むファイルです。
パーティション内のマージ
パーティションが有効になっている場合、ClickHouseはパーティション内でのみデータパーツをマージし、パーティション間ではマージしません。上記の例テーブルについてこれをスケッチします:

上記の図でスケッチされているように、異なるパーティションに属するパーツは決してマージされません。高いカーディナリティのパーティションキーが選択された場合、何千ものパーティションに分散されたパーツは、事前に構成された制限を超えてマージ候補にはなりません。この問題に対処するのは簡単です:カーディナリティが1000から10000未満の合理的なパーティションキーを選択してください。こちらをご覧ください。
パーティションの監視
例テーブルのすべての既存ユニークパーティションのリストをクエリすることができます。その際、仮想カラム_partition_value
を使用します:
あるいは、ClickHouseはsystem.partsシステムテーブルのすべてのテーブルのすべてのパーツとパーティションを追跡しており、以下のクエリはこちらの例テーブルに対してすべてのパーティションのリスト、アクティブなパーツの現在の数、およびこれらのパーツごとの行の合計を返します:
テーブルパーティションの使用用途
データ管理
ClickHouseでは、パーティショニングは主にデータ管理機能です。パーティション式に基づいて論理的にデータを整理することで、各パーティションを独立して管理できます。たとえば、上記の例テーブルのパーティショニングスキームを使用すると、TTLルールを使用して自動的に古いデータを削除することにより、過去12か月のデータのみがメインテーブルに保持されるシナリオを可能にします(DDL文の最後の行を参照)。
テーブルはtoStartOfMonth(date)
でパーティション分けされているため、TTL条件を満たす全体のパーティション(テーブルパーツのセット)が削除され、クリーンアップ操作がより効率的になり、パーツを再作成する必要がなくなります。
同様に、古いデータを削除する代わりに、よりコスト効率の高いストレージティアに自動的かつ効率的に移動することができます:
クエリ最適化
パーティションはクエリのパフォーマンスを助けることがありますが、これはアクセスパターンに大きく依存します。クエリがわずか数個のパーティション(理想的には1つ)のみにターゲットを絞示す場合、パフォーマンスは向上する可能性があります。これは、パーティショニングキーが主キーには含まれておらず、その条件でフィルタリングを行っている場合に通常役立ちます。以下のクエリ例のように。
このクエリは上記の例テーブル上で実行され、2020年12月にロンドンで売却されたすべての物件の最高販売価格をフィルタリングしながら計算します(date
カラムはテーブルのパーティションキーに使用され、town
カラムはテーブルの主キーに使用されていますが、date
は主キーの一部ではありません)。
ClickHouseは、このクエリを関連のないデータを評価しないようにプルーニング技術を適用して処理します:

① パーティションプルーニング: MinMaxインデックスは、テーブルのパーティションキーに使用されているカラムにかかるクエリのフィルターに論理的に一致しない全体のパーティション(パーツのセット)を無視するために使用されます。
② グラニュールプルーニング: ステップ①の後の残りのデータパーツに対して、主インデックスを使用して、主キーに使用されるカラムにかかるクエリのフィルターに論理的に一致しないすべてのグラニュール(行のブロック)を無視します。
これらのデータプルーニング手順を確認することで、上記の例クエリの物理的な実行計画をEXPLAIN句を使用して検査することができます:
上記の出力は次のことを示しています:
① パーティションプルーニング: EXPLAIN出力の行7から行18は、ClickHouseが最初にdate
フィールドのMinMaxインデックスを使用して、3257の既存グラニュールの中から11を特定し、1つのアクティブなデータパートに含まれる行のうち、クエリのdate
フィルターに一致する行を持つものを特定します。
② グラニュールプルーニング: EXPLAIN出力の行19から行24は、ClickHouseがステップ①で特定されたデータパートのtown
フィールドを対象にした主インデックスを使用して、クエリのtown
フィルターに一致する行も含む可能性があるグラニュールの数を11から1にさらに減らしたことを示しています。
その結果、ClickHouseは1つのグラニュール(8192行のブロック)をスキャンして6ミリ秒かけてクエリ結果を計算しました。
パーティショニングは主にデータ管理機能です
すべてのパーティションを横断したクエリは、通常、非パーティションテーブルで同じクエリを実行するよりも遅いことを認識してください。
パーティショニングを使用することで、データは通常、より多くのデータパーツに分散され、これによりClickHouseがスキャンおよび処理するデータの量が大きくなることがよくあります。
これを示すために、What are table partsの例テーブル(パーティショニングが有効でない)と、上記の現在の例テーブル(パーティショニングが有効である)で同じクエリを実行してみます。両方のテーブルには次のように同じデータと行数が含まれています:
ただし、パーティションが有効なテーブルは、こちらを参照してくださいアクティブなデータパーツがより多くあります。前述のように、ClickHouseはデータパーツをパーティション内でのみマージするためです:
上記に示したように、パーティション化されたテーブルuk_price_paid_simple_partitioned
は306のパーティションを持ち、したがって少なくとも306のアクティブなデータパーツを持っています。それに対して、非パーティションテーブルuk_price_paid_simple
のすべての初期データパーツは、バックグラウンドマージによって単一のアクティブパートにマージされることができました。
私たちがチェックする場合、物理的なクエリ実行計画をEXPLAIN句を使ってパーティションフィルターなしでパーティション化されたテーブルを実行した結果、出力の行19と20において、ClickHouseは3257の既存グラニュールのうち671を特定し、436のアクティブデータパーツに分散されており、クエリのフィルターに一致する可能性のある行を含むことを示しています。したがって、これらの行はクエリエンジンによってスキャンおよび処理されます:
非パーティションテーブルの同じ例クエリの物理的なクエリ実行計画はこちらで示されており、出力の行11と12でClickHouseがクエリのフィルターに一致する行を含む可能性があるテーブルの単一のアクティブデータパート内の3083の既存の行のブロックの中から241を特定したことを示しています:
パーティション化されたテーブルのクエリを実行する際に、ClickHouseは671の行のブロックをスキャンおよび処理し(約550万行)90ミリ秒かかります:
一方、非パーティションテーブルでクエリを実行した場合、ClickHouseは241のブロック(約200万行)をスキャンおよび処理し、12ミリ秒かかります: