月次パーティションを絞り込み、削除し、切り離し、コピーする
目標
月単位のパーティションのテーブルで、プルーニング(Min-Max)が何を捨てるかをEXPLAINで読み、パーティションのないテーブルと読んだ行数を比べます。DROP/DETACH/ATTACH PARTITIONとDELETEの違いをsystem.mutations・part_logで確認し、細かく分けすぎたパーティションがINSERTを止める様子を見ます。
なぜ重要なのか
パーティションキーは、一度決めると変えにくく、誤って選ぶと(細かすぎると)パートが爆発してINSERTが止まります。逆にうまく選べば、保管期間の管理がファイル単位で終わります。このラボの採点ツールは、書き込まれた数値をそのまま信用しません。保存されたSELECTにEXPLAINを付けて読み取り専用でもう一度実行し、パーティションの操作はテーブルの行ハッシュとsystem.mutations・part_log・query_logで突き合わせます。
ステップ
- データベース
ptnとテーブルptn.salesを作成してください。列はts DateTime, region LowCardinality(String), order_id UInt64, amount UInt32(この順序)、エンジンはMergeTree、PARTITION BY toYYYYMM(ts)、ORDER BY (region, ts)です。 /opt/lab/fixtures/partition/sales.sqlを1回だけ実行して150万行を入れ、OPTIMIZE TABLE ptn.sales FINALでまとめたあと、パーティションごとにpartition_id・part(パート名)・rowsを持つJSON配列を保存してください(保存先: /root/ch/partition/partitions.json)。- 2026年8月(
ts >= '2026-08-01 00:00:00' AND ts < '2026-09-01 00:00:00')のamountの合計を求めるクエリ(/root/ch/partition/q_aug.sql)を作成し、EXPLAIN indexes = 1の出力を保存してください(保存先: /root/ch/partition/explain_aug.txt)。Min-Max段階の残ったパートの数と全体のパートの数、およびこのクエリのrows_readを、parts_selected・parts_total・rows_readとして書き込んでください(保存先: /root/ch/partition/prune.json)。 - パーティションなしで同じ列・
ORDER BY (region, ts)のptn.sales_flatを作成し、ptn.salesの行を移してまとめてください。同じ8月の条件でそのテーブルを読むクエリ(/root/ch/partition/q_flat.sql)を作成し、2つのクエリのrows_readをpartitioned_rows_read・flat_rows_readとして書き込んでください(保存先: /root/ch/partition/compare.json)。 ptn.salesのコピーptn.s_drop・ptn.s_deleteを作成し(CREATE TABLE ... AS ptn.sales、行のコピー、マージ)、7月を削除してください。s_dropはDROP PARTITION 202607、s_deleteはALTER TABLE ... DELETE WHERE toYYYYMM(ts) = 202607 SETTINGS mutations_sync = 1を使います。2つのテーブルのsystem.mutationsの行数と、s_deleteのpart_logのMutatePartイベントの数を、drop_mutations・delete_mutations・delete_mutated_partsとして書き込んでください(保存先: /root/ch/partition/dropdel.json)。- コピー
ptn.s_detachを同じ方法で作成し、DETACH PARTITION 202609で切り離したあと、system.detached_partsに表示されるパート名を確認し、ATTACH PARTITION 202609でもう一度付け直してください。切り離すときの名前と、付け直したあとのアクティブなパート名を、detached_part・attached_partとして書き込んでください(保存先: /root/ch/partition/detach.json)。 CREATE TABLE ptn.sales_jul AS ptn.salesで空のテーブルを作成し、INSERTなしでALTER TABLE ptn.sales_jul ATTACH PARTITION 202607 FROM ptn.salesを使って、7月をコピーしてください。- 同じ列で
PARTITION BY (toDate(ts), region)・ORDER BY tsのptn.sales_fineを作成し、ptn.salesの全体をINSERTしてみて、出たエラーを保存してください(保存先: /root/ch/partition/toofine.txt)。ptn.salesで日付と地域の組み合わせがいくつあるかを数えて、partitions_neededとして書き込んでください(保存先: /root/ch/partition/fine.json)。
参考
- パーティションの確認:
SELECT partition_id, name, rows FROM system.parts WHERE database = 'ptn' AND table = '...' AND active。 - EXPLAINには、Min-Max・Partition・PrimaryKeyの段階が順に出ます。このラボが尋ねているのは、Min-Max段階の
Parts: 남은/전체(プレースホルダーは残ったパート数と全体のパート数です)です。 - 読んだ行数は、
clickhouse-client --use_query_condition_cache 0 --queries-file q_aug.sql --format JSON | jq .statistics.rows_readで測ります(クエリ条件キャッシュを無効にして)。 CREATE TABLE 새표 AS ptn.salesはPARTITION BYまでコピーします(プレースホルダーは新しいテーブル名です)。ステップ4のようにパーティションのないテーブルは、列を自分で書いて作成してください。- よくある間違い: ステップ6で、付け直したあとの名前を、切り離すときの名前として書くことです(付け直すと新しいブロック番号を受け取ります)。ステップ8で
max_partitions_per_insert_blockを上げて通してしまうことです。このステップは、拒否されるのを見るステップです。 - 公式ドキュメント: Table partitions・Choosing a partitioning key・Custom partitioning key・ALTER ... PARTITION・ALTER ... DELETE・EXPLAIN
月単位のパーティションのテーブルを作成する
データベースptnとテーブルptn.salesを作成してください。列はts DateTime, region LowCardinality(String), order_id UInt64, amount UInt32の順序、エンジンはMergeTree、PARTITION BY toYYYYMM(ts)、ORDER BY (region, ts)です。
PARTITION BYは、ORDER BYの前に書いても後ろに書いてもかまいません。toYYYYMM(ts)は202607のような整数を返し、この値がそのままpartition_idになります。ソートキーにパーティションキーを入れなくてもかまいません。1つのパートの中では、どのみち同じ月だからです。
3か月分を入れてパーティションごとのパートを書き込む
/opt/lab/fixtures/partition/sales.sqlを1回だけ実行して1,500,000行を入れ、OPTIMIZE TABLE ptn.sales FINALでまとめてください。パーティションごとにpartition_id・part(アクティブなパート名)・rowsを持つオブジェクトのJSON配列を保存してください(保存先: /root/ch/partition/partitions.json)。
1回のINSERTでも、パーティションが3つならパートも少なくとも3つになります。FINALはパーティションごとに別々にまとめるので、まとめたあともパートはパーティションの数だけ残ります。JSONEachRowで受け取った行をjq -s .でまとめると、配列になります。
8月だけで絞り込むと: Min-Maxプルーニング
2026年8月(ts >= '2026-08-01 00:00:00' AND ts < '2026-09-01 00:00:00')のamountの合計を求めるクエリ(/root/ch/partition/q_aug.sql)を作成し、そのSELECTにEXPLAIN indexes = 1を付けた出力を保存してください(保存先: /root/ch/partition/explain_aug.txt)。Min-Max段階のParts: 남은/전체(プレースホルダーは残ったパート数と全体のパート数です)と、このクエリのrows_read(クエリ条件キャッシュを無効にして)を、parts_selected・parts_total・rows_readとして書き込んでください(保存先: /root/ch/partition/prune.json)。
パートごとに、パーティションキーに使われた列(ts)の最小値・最大値が記録されていて、範囲が重ならないパートは開きません。Min-Maxの次のPartition段階は、残ったパートだけをもう一度見ます。読んだ行数を、8月のパーティションの行数と見比べてみてください。
パーティションを外すとどれだけ多く読むか
パーティションなしで同じ列・ORDER BY (region, ts)のptn.sales_flatを作成し、ptn.salesの行を移してOPTIMIZE ... FINALでまとめてください。同じ8月の条件でそのテーブルのamountの合計を求めるクエリ(/root/ch/partition/q_flat.sql)を作成し、q_aug.sqlとq_flat.sqlのrows_readを、partitioned_rows_read・flat_rows_readとして書き込んでください(保存先: /root/ch/partition/compare.json)。
CREATE TABLE ... AS ptn.salesはPARTITION BYまでコピーするので、列を自分で書いて作成します。パーティションがなくても、tsはソートキーの2番目の列で、前の列のregionは5種類しかありません。前のモジュールのgeneric exclusion searchが何をしてくれるのかを、思い出してみてください。
同じ7月を2つの方法で削除する
ptn.salesのコピーptn.s_drop・ptn.s_deleteを作成し(CREATE TABLE ... AS ptn.sales、行のコピー、OPTIMIZE ... FINAL)、7月を削除してください。ptn.s_dropはALTER TABLE ptn.s_drop DROP PARTITION 202607、ptn.s_deleteはALTER TABLE ptn.s_delete DELETE WHERE toYYYYMM(ts) = 202607 SETTINGS mutations_sync = 1を使います。2つのテーブルのsystem.mutationsの行数と、s_deleteのpart_logのMutatePartイベントの数を、drop_mutations・delete_mutations・delete_mutated_partsとして書き込んでください(保存先: /root/ch/partition/dropdel.json)。
DROP PARTITIONは、パートをテーブルから切り離す操作なので、行を読むことも書くこともしません。ALTER DELETEはミューテーションになり、パートを新しいバージョンに書き直します。削除する行がないパートも新しいバージョンになるのかを、part_logで数えてみてください。mutations_sync = 1は、ミューテーションが終わるまで待たせます。
切り離して付け直すと名前が変わる
コピーptn.s_detachをステップ5と同じ方法で作成し、ALTER TABLE ptn.s_detach DETACH PARTITION 202609で切り離してください。system.detached_partsに表示されるパート名を確認したあと、ATTACH PARTITION 202609でもう一度付け直し、切り離すときの名前と、付け直したあとの202609のアクティブなパート名を、detached_part・attached_partとして書き込んでください(保存先: /root/ch/partition/detach.json)。
DETACHは削除せずにdetached/ディレクトリへ移し、その間テーブルはそのパートを忘れます(count()が減ります)。付け直されたパートは、テーブルで新しいブロック番号を受け取るので、名前の真ん中の数字が変わります。切り離した直後に、名前を変数に入れておいてください。
INSERTなしで1か月分をコピーしてくる
CREATE TABLE ptn.sales_jul AS ptn.salesで構造が同じ空のテーブルを作成し、ALTER TABLE ptn.sales_jul ATTACH PARTITION 202607 FROM ptn.salesで7月のパーティションをコピーしてください。ptn.salesはそのまま150万行である必要があり、ptn.sales_julにはINSERTをしないでください。
ATTACH PARTITION ... FROMは、コピー元からもコピー先からも削除せずにパーティションをコピーします。2つのテーブルの構造とパーティションキーが同じである必要があります。採点ツールは、query_logでこのテーブルへのINSERTがなかったかも確認します。INSERT ... SELECTで埋めると、同じ行でも不合格になります。
細かく分けすぎたパーティション
同じ列でPARTITION BY (toDate(ts), region)・ORDER BY tsのptn.sales_fineを作成し、INSERT INTO ptn.sales_fine SELECT * FROM ptn.salesを実行して、出たエラー(stderr)を保存してください(保存先: /root/ch/partition/toofine.txt)。ptn.salesで日付と地域の組み合わせがいくつあるかを数えて、partitions_neededとして書き込んでください(保存先: /root/ch/partition/fine.json)。
1つのINSERTのブロックが作るパーティション数には上限(max_partitions_per_insert_block)があり、超えるとブロック全体が拒否されます。3か月×地域5つなら、いくつになるでしょうか。組み合わせの数はuniqExact(toDate(ts), region)で数えます。上限を上げて通してはいけません。