ClickHouse — 열 지향 분석 DB 를 속까지 · ReplacingMergeTree · 实验
병합 전의 중복을 보고, FINAL·argMax·CLEANUP 으로 다룬다
목표
ReplacingMergeTree 에 변경 기록(갱신·탈퇴)을 넣고, 병합 전에는 옛 버전이 보인다는 것과 그것을 FINAL·argMax 로 가리는 법, 병합과 CLEANUP 이 실제로 무엇을 지우는지, 정렬 키와 재전송이 결과를 어떻게 바꾸는지 숫자로 확인합니다.
왜 중요한가
분석 DB 로 옮긴 운영 데이터는 계속 바뀝니다. ClickHouse 는 갱신을 "새 버전 INSERT + 나중 병합" 으로 처리하는데, 병합은 언제 일어날지 모릅니다. 그래서 같은 쿼리가 어떤 때는 맞고 어떤 때는 틀립니다. 이 실습은 SYSTEM STOP MERGES 로 "병합 전" 을 고정해 그 틀린 답을 눈으로 보고, 조회 시점에 맞는 답을 내는 두 방법을 익힙니다. 채점기는 기대값을 원본 생성 식에서 직접 계산하고, 병합이 실제로 일어났는지는 system.part_log 의 기록으로 확인합니다.
단계
1. 데이터베이스 rmt 와 표 rmt.users 를 만드세요 — 열 user_id UInt64, email String, plan LowCardinality(String), score UInt32, ver UInt32, is_deleted UInt8(이 순서), 엔진 ReplacingMergeTree(ver, is_deleted), ORDER BY user_id.
2. SYSTEM STOP MERGES rmt.users 로 이 표의 병합을 멈춘 뒤 /opt/lab/fixtures/replacing/changes.sql 을 한 번 실행하세요(INSERT 세 번 = 파트 세 개).
3. FINAL 없이 전체 행을 세는 /root/ch/replacing/q_raw.sql 과 FROM rmt.users FINAL 로 세는 /root/ch/replacing/q_final.sql 을 만들고, 두 결과를 /root/ch/replacing/counts.json 에 raw·final 로 적으세요.
4. FINAL 을 쓰지 않고 요금제(plan)별 현재 사용자 수를 내는 /root/ch/replacing/q_plan.sql 을 만드세요 — 결과는 plan 과 사용자 수 두 열입니다.
5. rmt.merged 를 rmt.users 와 같은 열·엔진으로 만들어 rmt.users 의 행을 ver = 1, ver = 2, ver = 3 순으로 INSERT 세 번에 옮기고 OPTIMIZE TABLE rmt.merged FINAL 을 실행한 뒤, rows_after_merge(count())·deleted_rows_kept(is_deleted = 1 인 행 수)·final_count(FINAL 로 센 수)를 /root/ch/replacing/merged.json 에 적으세요.
6. rmt.cleaned 를 같은 열·엔진에 표 설정 allow_experimental_replacing_merge_with_cleanup = 1 을 붙여 만들고, 같은 방법으로 세 번 넣은 뒤 OPTIMIZE TABLE rmt.cleaned FINAL CLEANUP 을 실행해 rows_after_cleanup·deleted_rows_left 를 /root/ch/replacing/cleanup.json 에 적으세요.
7. 같은 열·엔진이지만 정렬 키가 ORDER BY (user_id, plan) 인 rmt.bad 를 만들어 같은 방법으로 세 번 넣고 OPTIMIZE TABLE rmt.bad FINAL 한 뒤, good_final(rmt.merged 를 FINAL 로 센 수)·bad_final(rmt.bad 를 FINAL 로 센 수)·bad_users(rmt.bad FINAL 의 서로 다른 user_id 수)를 /root/ch/replacing/key.json 에 적으세요.
8. 늦은 재전송을 흉내 냅니다 — rmt.users 에서 ver = 1 AND user_id <= 1000 인 행을 rmt.merged 와 rmt.cleaned 에 각각 다시 넣고, 두 표를 FINAL 로 센 값과 그 차이를 /root/ch/replacing/replay.json 에 merged_final·cleaned_final·resurrected 로 적으세요.
참고
- 서버는 파드가 뜰 때 이미 떠 있습니다.
clickhouse-client만 치면 붙습니다. 멈췄다면ch-up(서버를 다시 띄우면 STOP MERGES 가 풀립니다 — 2단계부터 다시). SYSTEM STOP MERGES는 그 표에만 걸립니다. 5–7단계의 새 표는 병합이 켜져 있어 OPTIMIZE 가 됩니다. 멈춘 표에 OPTIMIZE 를 내면 "Cancelled merging parts" 로 거절됩니다.- 병합 기록은
system.part_log에 남습니다(event_type = 'MergeParts',merged_from에 합쳐진 파트 이름). 방금 일이 안 보이면SYSTEM FLUSH LOGS. - 흔한 실수: 5–7단계에서
INSERT ... SELECT * FROM rmt.users한 번으로 옮기는 것 — 한 INSERT 안의 중복은 넣는 순간 줄어들어(optimize_on_insert) 병합할 것이 없어집니다. 버전별로 세 번 넣으세요. - 숫자를 JSON 에 따옴표 없이 받으려면
--output_format_json_quote_64bit_integers 0. - 공식 문서: [ReplacingMergeTree](https://clickhouse.com/docs/reference/engines/table-engines/mergetree-family/replacingmergetree) · [Working with the ReplacingMergeTree engine](https://clickhouse.com/docs/concepts/features/operations/update/replacing-merge-tree) · [OPTIMIZE](https://clickhouse.com/docs/reference/statements/optimize) · [system.part_log](https://clickhouse.com/docs/reference/system-tables/part_log)
8个步骤
- 버전과 삭제 표시가 있는 표
- 병합을 멈추고 변경 기록 세 묶음을 넣는다
- FINAL 없이 세기와 FINAL 로 세기
- FINAL 없이 같은 답 — argMax
- 병합해도 탈퇴 행은 남는다
- CLEANUP 병합은 탈퇴 행까지 지운다
- 정렬 키가 "같은 행" 을 정한다
- CLEANUP 뒤에 옛 행이 다시 오면