ClickHouse — A Columnar Analytics Database from the Inside
Prune, drop, detach and copy monthly partitions
한국어 원문으로 표시합니다.
목표
월 파티션 표에서 가지치기(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을 한 번 실행해 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를 /root/ch/partition/prune.json 에parts_selected·parts_total·rows_read로 적으세요. - 파티션 없이 같은 열·
ORDER BY (region, ts)인ptn.sales_flat을 만들어ptn.sales의 행을 옮기고 합치세요. 같은 8월 조건으로 그 표를 읽는 /root/ch/partition/q_flat.sql 을 만들고, 두 쿼리의rows_read를 /root/ch/partition/compare.json 에partitioned_rows_read·flat_rows_read로 적으세요. 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. 두 표의 system.mutations 행 수와s_delete의 part_logMutatePart사건 수를 /root/ch/partition/dropdel.json 에drop_mutations·delete_mutations·delete_mutated_parts로 적으세요.- 사본
ptn.s_detach를 같은 방법으로 만들어DETACH PARTITION 202609로 떼어 낸 뒤 system.detached_parts 에 보이는 파트 이름을 확인하고,ATTACH PARTITION 202609로 다시 붙이세요. 뗄 때의 이름과 다시 붙은 뒤의 활성 파트 이름을 /root/ch/partition/detach.json 에detached_part·attached_part로 적으세요. 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에서 (날짜, 지역) 조합이 몇 개인지 세어 /root/ch/partition/fine.json 에partitions_needed로 적으세요.
참고
- 파티션 보기:
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 가 됩니다. 정렬 키에는 파티션 키를 넣지 않아도 됩니다 — 한 파트 안에서는 어차피 같은 달입니다.
석 달치를 넣고 파티션별 파트를 적는다
/opt/lab/fixtures/partition/sales.sql 을 한 번 실행해 1,500,000행을 넣고 OPTIMIZE TABLE ptn.sales FINAL 로 합치세요. 파티션마다 partition_id·part(활성 파트 이름)·rows 를 가진 객체의 JSON 배열을 /root/ch/partition/partitions.json 에 저장하세요.
INSERT 한 번이라도 파티션이 셋이면 파트도 적어도 셋입니다. 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(쿼리 조건 캐시를 끄고)를 /root/ch/partition/prune.json 에 parts_selected·parts_total·rows_read 로 적으세요.
파트마다 파티션 키에 쓰인 열(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 를 /root/ch/partition/compare.json 에 partitioned_rows_read·flat_rows_read 로 적으세요.
CREATE TABLE ... AS ptn.sales 는 PARTITION BY 까지 복사하므로 열을 직접 적어 만듭니다. 파티션이 없어도 ts 는 정렬 키의 두 번째 열이고 앞 열 region 은 다섯 가지뿐입니다 — 앞 모듈의 generic exclusion search 가 무엇을 해 주는지 떠올려 보세요.
같은 7월을 두 방법으로 지운다
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. 두 표의 system.mutations 행 수와 s_delete 의 part_log MutatePart 사건 수를 /root/ch/partition/dropdel.json 에 drop_mutations·delete_mutations·delete_mutated_parts 로 적으세요.
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 활성 파트 이름을 /root/ch/partition/detach.json 에 detached_part·attached_part 로 적으세요.
DETACH 는 지우지 않고 detached/ 디렉터리로 옮기며, 그동안 표는 그 파트를 잊습니다(count() 가 줄어듭니다). 다시 붙은 파트는 표에서 새 블록 번호를 받으므로 이름의 가운데 숫자가 달라집니다. 떼어 낸 직후에 이름을 변수에 담아 두세요.
INSERT 없이 한 달을 복사해 온다
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 은 원본에서도 대상에서도 지우지 않고 파티션을 복사합니다. 두 표의 구조와 파티션 키가 같아야 합니다. 채점기는 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 에서 (날짜, 지역) 조합이 몇 개인지 세어 /root/ch/partition/fine.json 에 partitions_needed 로 적으세요.
INSERT 한 블록이 만들 파티션 수에는 상한(max_partitions_per_insert_block)이 있고, 넘으면 블록 전체가 거절됩니다. 석 달 × 지역 다섯이면 몇 개일까요? 조합 수는 uniqExact(toDate(ts), region) 으로 셉니다. 상한을 올려 통과시키지 마세요.