MergeTree 엔진 패밀리와 인덱스
PRIMARY KEY와 ORDER BY
MergeTree 테이블을 생성할 때 핵심 설정 두 가지.
- ORDER BY: 파트 내 행이 물리적으로 정렬되는 기준. 범위 쿼리 성능과 데이터 압축 효율에 직접 영향을 준다.
- PRIMARY KEY: 스파스 인덱스에 기록될 컬럼. 생략하면 ORDER BY와 동일하게 적용된다.
CREATE TABLE events (
event_date Date,
user_id UInt64,
event_type String,
value Float32
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id);ClickHouse의 PRIMARY KEY는 RDBMS처럼 유일성(uniqueness)을 강제하지 않는다. 중복을 허용하며, 정렬 기준이자 스파스 인덱스의 탐색 키로만 사용된다.
PARTITION BY는 선택 사항이다. 시간 범위로 파티션을 나누면 쿼리가 해당 파티션만 스캔해 I/O를 먼저 줄인다. 파티션 프루닝은 스파스 인덱스 스킵보다 더 앞 단계에서 일어난다.
스파스 인덱스와 그래뉼
ClickHouse는 B-Tree 행 수준 인덱스 대신 스파스(sparse) 기본 키 인덱스를 사용한다.
핵심 단위는 그래뉼(granule) — 기본값 8,192행 묶음이다.
- 파트 내 행들은
ORDER BY순으로 정렬된 뒤 그래뉼 단위로 분할된다. - 스파스 인덱스(
primary.idx)는 그래뉼당 첫 행의 키 값만 저장한다. - 쿼리가 들어오면 인덱스를 이진 탐색해 해당 범위의 그래뉼 번호를 구하고, 그 그래뉼에 해당하는 컬럼 파일 블록만 읽는다.
10억 row 테이블이라면 인덱스 항목 수는 약 12만 개(= 10억 / 8192)다. 이 인덱스 전체가 메모리에 올라가고, 나머지 데이터는 필요한 그래뉼만 디스크에서 읽는다. B-Tree처럼 모든 row를 인덱싱하지 않아도 petabyte 규모에서 인덱스가 메모리를 압도하지 않는다.
EXPLAIN indexes = 1 SELECT ...으로 실제로 몇 개 그래뉼을 읽는지 확인할 수 있다.
보조 Skip 인덱스
ORDER BY에 없는 컬럼에도 INDEX 구문으로 스킵 인덱스를 추가할 수 있다. 스킵 인덱스는 그래뉼 단위로 "이 그래뉼에는 조건을 만족하는 행이 없다"는 사실을 미리 알려줘 I/O를 추가로 줄인다.
ALTER TABLE events
ADD INDEX idx_type (event_type) TYPE bloom_filter(0.01) GRANULARITY 4;주요 타입:
| 타입 | 동작 방식 | 적합한 경우 |
|---|---|---|
| minmax | 그래뉼 내 최솟값·최댓값 저장 | 숫자·날짜 범위 조건 |
| bloom_filter | 집합 존재 여부 확률적 확인 | 고카디널리티 문자열 |
| set(N) | 고유값 집합 저장 | 저카디널리티 컬럼 |
스킵 인덱스는 잘못 설계하면 오히려 쿼리 경로에 부담이 된다. 실제 쿼리 패턴을 먼저 분석하고 추가하는 것이 좋다.
MergeTree 패밀리
MergeTree는 다양한 파생 엔진의 기반이다. 핵심 차이는 병합 시 추가로 수행하는 동작이다.
| 엔진 | 병합 시 추가 동작 | 사용 사례 |
|---|---|---|
| MergeTree | 없음 (단순 병합) | 일반 이벤트 로그, append-only |
| ReplacingMergeTree | 같은 ORDER BY 키 중 최신 버전(지정 컬럼 기준) 1개만 보존 | OLTP 미러, Upsert 시뮬레이션 |
| SummingMergeTree | 같은 키의 숫자 컬럼 합산 | 집계 중간 결과 저장 |
| AggregatingMergeTree | AggregateFunction 상태 병합 | Materialized View 집계 |
| CollapsingMergeTree | sign 컬럼(+1/-1)으로 행 취소 | 변경 이력 CDC 적용 |
| ReplicatedMergeTree | ZooKeeper/ClickHouse Keeper로 레플리카 간 동기화 | 고가용성 운영 |
ReplicatedMergeTree는 독립 엔진이 아니라 복제 레이어를 얹는 방식이다. ReplicatedReplacingMergeTree, ReplicatedSummingMergeTree처럼 어떤 파생 엔진에도 조합할 수 있다.
Distributed 테이블과 2-레이어 아키텍처
운영 환경에서 ClickHouse 클러스터의 표준 구조다.
- 로컬 테이블: 각 샤드에
ReplicatedMergeTree를 배치. ZooKeeper/ClickHouse Keeper를 통해 같은 샤드 내 레플리카 간 데이터를 동기화한다. - Distributed 테이블: 모든 샤드에 쿼리를 팬아웃하고 결과를 병합. 데이터 자체는 저장하지 않는 라우터다.
샤딩은 수평 확장, 레플리케이션은 고가용성을 위한 것이다. 두 축을 분리해서 설계하는 것이 ClickHouse 클러스터 설계의 시작점이다.
쓰기는 Distributed 테이블에 직접 하거나(Distributed가 각 샤드로 분산), 로컬 테이블에 직접 써도 된다. 읽기는 Distributed 테이블 하나로 전 샤드를 조회한다.
한 줄 정리
ClickHouse MergeTree는 스파스 인덱스로 그래뉼 단위 I/O 스킵을, 엔진 패밀리로 용도별 병합 동작을, ReplicatedMergeTree + Distributed로 분산·고가용성을 제공한다.
References
- https://clickhouse.com/docs/engines/table-engines/mergetree-family/mergetree
- https://clickhouse.com/docs/primary-indexes
- https://pulse.support/kb/clickhouse-index
- https://medium.com/@anshs5103/clickhouse-under-the-hood-mergetree-lsm-architecture-and-disk-level-optimizations-13ea85fc684d
- https://clickhouse.com/blog/clickhouse-25-6-coalescingmergetree
- https://sairamkrish.medium.com/clickhouse-sharding-and-replication-95b3275c873e
- https://oneuptime.com/blog/post/2026-03-31-clickhouse-data-skipping-sparse-indexes/view