LabHub
배우기 러닝패스 코스

ClickHouse — 列指向分析 DB を中身から

月次パーティションを絞り込み、削除し、切り離し、コピーする

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

월 파티션 표에서 가지치기(Min-Max)가 무엇을 버리는지 EXPLAIN 으로 읽고, 파티션 없는 표와 읽은 행을 비교한다. DROP/DETACH/ATTACH PARTITION 과 DELETE 의 차이를 system.mutations·part_log 로 확인하고, 너무 잘게 나눈 파티션이 INSERT 를 막는 것을 본다.

왜 중요한가

파티션 키는 한 번 정하면 바꾸기 어렵고, 잘못 고르면(너무 잘게) 파트가 폭증해 INSERT 가 막힌다. 반대로 잘 고르면 보관 주기 관리가 파일 단위로 끝난다. 이 실습의 채점기는 여러분이 적은 숫자를 믿지 않는다 — 저장한 SELECT 에 EXPLAIN 을 붙여 읽기 전용으로 다시 돌리고, 파티션 조작은 표의 행 해시와 system.mutations·part_log·query_log 로 대조한다.

단계

  1. 데이터베이스 ptn 과 표 ptn.sales 를 만드세요 — 열 ts DateTime, region LowCardinality(String), order_id UInt64, amount UInt32 (이 순서), 엔진 MergeTree, PARTITION BY toYYYYMM(ts), ORDER BY (region, ts).
  2. /opt/lab/fixtures/partition/sales.sql한 번 실행해 150만 행을 넣고 OPTIMIZE TABLE ptn.sales FINAL 로 합친 뒤, 파티션마다 partition_id·part(파트 이름)·rows 를 담은 JSON 배열을 /root/ch/partition/partitions.json 에 저장하세요.
  3. 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.jsonparts_selected·parts_total·rows_read 로 적으세요.
  4. 파티션 없이 같은 열·ORDER BY (region, ts)ptn.sales_flat 을 만들어 ptn.sales 의 행을 옮기고 합치세요. 같은 8월 조건으로 그 표를 읽는 /root/ch/partition/q_flat.sql 을 만들고, 두 쿼리의 rows_read/root/ch/partition/compare.jsonpartitioned_rows_read·flat_rows_read 로 적으세요.
  5. ptn.sales 의 사본 ptn.s_drop·ptn.s_delete 를 만들어(CREATE TABLE ... AS ptn.sales + 행 복사 + 합치기) 7월을 지우세요 — s_dropDROP PARTITION 202607, s_deleteALTER TABLE ... DELETE WHERE toYYYYMM(ts) = 202607 SETTINGS mutations_sync = 1. 두 표의 system.mutations 행 수와 s_delete 의 part_log MutatePart 사건 수를 /root/ch/partition/dropdel.jsondrop_mutations·delete_mutations·delete_mutated_parts 로 적으세요.
  6. 사본 ptn.s_detach 를 같은 방법으로 만들어 DETACH PARTITION 202609 로 떼어 낸 뒤 system.detached_parts 에 보이는 파트 이름을 확인하고, ATTACH PARTITION 202609 로 다시 붙이세요. 뗄 때의 이름과 다시 붙은 뒤의 활성 파트 이름을 /root/ch/partition/detach.jsondetached_part·attached_part 로 적으세요.
  7. CREATE TABLE ptn.sales_jul AS ptn.sales 로 빈 표를 만들고, INSERT 없이 ALTER TABLE ptn.sales_jul ATTACH PARTITION 202607 FROM ptn.sales 로 7월을 복사해 오세요.
  8. 같은 열에 PARTITION BY (toDate(ts), region)·ORDER BY tsptn.sales_fine 을 만들어 ptn.sales 전체를 INSERT 해 보고, 나온 오류를 /root/ch/partition/toofine.txt 에 저장하세요. ptn.sales 에서 (날짜, 지역) 조합이 몇 개인지 세어 /root/ch/partition/fine.jsonpartitions_needed 로 적으세요.

참고

월 파티션 표를 만든다

데이터베이스 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.jsonparts_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.sqlq_flat.sqlrows_read/root/ch/partition/compare.jsonpartitioned_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_dropALTER TABLE ptn.s_drop DROP PARTITION 202607, ptn.s_deleteALTER TABLE ptn.s_delete DELETE WHERE toYYYYMM(ts) = 202607 SETTINGS mutations_sync = 1. 두 표의 system.mutations 행 수와 s_delete 의 part_log MutatePart 사건 수를 /root/ch/partition/dropdel.jsondrop_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.jsondetached_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 tsptn.sales_fine 을 만들고 INSERT INTO ptn.sales_fine SELECT * FROM ptn.sales 를 실행해, 나온 오류(stderr)를 /root/ch/partition/toofine.txt 에 저장하세요. ptn.sales 에서 (날짜, 지역) 조합이 몇 개인지 세어 /root/ch/partition/fine.jsonpartitions_needed 로 적으세요.

INSERT 한 블록이 만들 파티션 수에는 상한(max_partitions_per_insert_block)이 있고, 넘으면 블록 전체가 거절됩니다. 석 달 × 지역 다섯이면 몇 개일까요? 조합 수는 uniqExact(toDate(ts), region) 으로 셉니다. 상한을 올려 통과시키지 마세요.