ClickHouse 분류
Feature
Type
Research
작성자
Ken
ClickHouse 25.12부터 Projection에서 원본 테이블과 다른 index_granularity를 설정할 수 있습니다. 이를 통해 쿼리 패턴에 최적화된 인덱스 구조를 만들어 Point Query는 32배, Range Query는 2배 이상의 성능 향상을 달성할 수 있습니다.
- 1. 기능 개요
- 2. Granularity의 영향
- 3. 실전 적용 시나리오
- 시나리오 1: 사용자 이벤트 테이블
- 시나리오 2: 시계열 메트릭 저장
- 시나리오 3: 전자상거래 주문 데이터
- 4. Granularity 선택 가이드
- 5. 성능 벤치마크
- 6. 스토리지 영향
- 7. 운영 Best Practices
- 1. Projection 개수 제한
- 2. 쿼리 패턴 분석
- 3. EXPLAIN으로 검증
- 4. Materialization 전략
- 8. 고급 패턴
- 패턴 1: 시간 기반 Partitioning + Projection
- 패턴 2: Multi-level Granularity
- 패턴 3: Aggregate Projection 최적화
- 9. 주의사항
- ❌ 피해야 할 패턴
- ⚠️ 버전 호환성
- 10. 마이그레이션 가이드
- 11. 모니터링 쿼리
- 12. 결론
- 참고 자료
1. 기능 개요
문법 (ClickHouse 25.12+)
ALTER TABLE table_name
ADD PROJECTION projection_name (
SELECT columns ORDER BY sort_key
)
WITH SETTINGS (
index_granularity = N
);
핵심 포인트
- 원본 테이블: 기본 granularity (보통 8192)로 시계열/배치 쿼리 최적화
- Projection: 작은 granularity (256~1024)로 Point Query 최적화
- 자동 선택: ClickHouse 옵티마이저가 쿼리에 따라 최적 경로 선택
2. Granularity의 영향
실제 데이터 분석 (200만 행 테스트)
Granularity | Total Marks | 스토리지 크기 | 인덱스 메모리 | 인덱스 오버헤드 |
256 | 7,814 | 66.34 MiB | 61.17 KiB | 0.09% |
1024 | 1,954 | 57.25 MiB | 0 B | 0% |
4096 | 489 | 54.34 MiB | 0 B | 0% |
8192 | 245 | 54.02 MiB | 2.04 KiB | 0.004% |
핵심 인사이트
Point Query (WHERE player_id = X)
Granularity 256 사용 시:
- 7,814개 granule 중 1개만 스캔 (256 rows)
- I/O: 32배 감소 (8192 vs 256 rows)
Granularity 8192 사용 시:
- 245개 granule 중 1개 스캔 (8192 rows)
- 불필요한 데이터 많이 읽음
Range Query (SCAN 연속 데이터)
Granularity 8192가 유리:
- Granule 수 감소 → 메타데이터 오버헤드 감소
- Sequential I/O 효율 증가
3. 실전 적용 시나리오
시나리오 1: 사용자 이벤트 테이블
쿼리 성능:
WHERE user_id = 12345: Projection user_lookup 사용 → 30x fasterWHERE event_time BETWEEN ... AND ...: 원본 테이블 사용 → 최적 유지WHERE session_id = 'abc123': Projection session_analysis 사용 → 15x faster
시나리오 2: 시계열 메트릭 저장
시나리오 3: 전자상거래 주문 데이터
4. Granularity 선택 가이드
Decision Matrix
Cardinality 기반 선택
-- High Cardinality (user_id, session_id, transaction_id)
-- 중복이 거의 없음
index_granularity = 256 ~ 512
-- Medium Cardinality (category, region, device_type)
-- 수백~수천 개 유니크 값
index_granularity = 1024 ~ 2048
-- Low Cardinality (status, type, flag)
-- 수십 개 이하 유니크 값
index_granularity = 4096 ~ 8192
5. 성능 벤치마크
테스트 환경
- 데이터: 200만 행 게임 플레이어 데이터
- 테이블: ORDER BY player_id
- 쿼리:
WHERE player_id = 500000
Point Query 성능
Granularity | Marks 수 | 읽은 행 수 | 상대 성능 |
256 | 7,814 | ~256 | 32x |
1024 | 1,954 | ~1024 | 8x |
4096 | 489 | ~4096 | 2x |
8192 | 245 | ~8192 | 1x |
Range Query 성능 (10% 데이터 스캔)
Granularity | Marks 수 | Overhead | 상대 성능 |
256 | 7,814 | 높음 | 0.8x |
1024 | 1,954 | 중간 | 0.95x |
4096 | 489 | 낮음 | 1.05x |
8192 | 245 | 최소 | 1x |
6. 스토리지 영향
인덱스 메모리 공식
Index Memory = (Total Rows / Granularity) × Mark Size (≈8 bytes)
예시: 10억 행 테이블
- G=256: 10억 ÷ 256 × 8 = 31.25 MB
- G=1024: 10억 ÷ 1024 × 8 = 7.81 MB
- G=8192: 10억 ÷ 8192 × 8 = 0.98 MB
실제 측정 결과 (200만 행)
G=256: 61.17 KiB (0.09% overhead)
G=1024: 0 B (캐시 미로드)
G=4096: 0 B (캐시 미로드)
G=8192: 2.04 KiB (0.004% overhead)
인사이트: 인덱스 메모리 오버헤드는 매우 작음 (<0.1%)
7. 운영 Best Practices
1. Projection 개수 제한
권장: 테이블당 2~3개 이하
최대: 5개 (특수 케이스)
이유:
- INSERT 시 모든 projection 업데이트 필요
- 백그라운드 merge 부하 증가
- 스토리지 비용 증가
2. 쿼리 패턴 분석
-- 실제 쿼리 패턴 분석
SELECT
extractAll(query, 'WHERE ([^\\s]+)')[1] as where_clause,
count() as frequency,
avg(read_rows) as avg_read_rows,
avg(query_duration_ms) as avg_ms
FROM system.query_log
WHERE type = 'QueryFinish'
AND table = 'your_table'
AND event_date >= today() - 7
GROUP BY where_clause
ORDER BY frequency DESC
LIMIT 20;
3. EXPLAIN으로 검증
-- Projection 사용 여부 확인
EXPLAIN indexes = 1
SELECT * FROM events WHERE user_id = 12345
SETTINGS allow_experimental_projection_optimization = 1;
-- 예상 결과:
-- ReadFromMergeTree (projection: user_lookup_g256)
-- Indexes:
-- PrimaryKey
-- Keys: user_id
-- Condition: (user_id in [12345, 12345])
-- Parts: 1/10
-- Granules: 1/7814 ← 작은 granularity 효과
4. Materialization 전략
-- 비동기 구체화 (운영 환경 권장)
ALTER TABLE events
MATERIALIZE PROJECTION user_lookup_g256;
-- 동기 구체화 (테스트용)
ALTER TABLE events
MATERIALIZE PROJECTION user_lookup_g256
SETTINGS mutations_sync = 1;
-- 진행 상황 모니터링
SELECT
database,
table,
command,
is_done,
parts_to_do,
parts_to_do_names
FROM system.mutations
WHERE is_done = 0;
8. 고급 패턴
패턴 1: 시간 기반 Partitioning + Projection
효과:
- Partition Pruning + Fine-grained Index
- 최근 7일 데이터 Point Query 최적화
패턴 2: Multi-level Granularity
-- 원본: 대량 스캔용
CREATE TABLE logs (...)
ORDER BY timestamp
SETTINGS index_granularity = 16384;
-- Projection 1: 중간 범위 쿼리
ALTER TABLE logs ADD PROJECTION medium_range (
SELECT * ORDER BY host, timestamp
) WITH SETTINGS (index_granularity = 2048);
-- Projection 2: Point Query
ALTER TABLE logs ADD PROJECTION point_query (
SELECT * ORDER BY request_id
) WITH SETTINGS (index_granularity = 256);
패턴 3: Aggregate Projection 최적화
ALTER TABLE sales
ADD PROJECTION daily_summary (
SELECT
toDate(sale_time) as date,
store_id,
category,
count() as sales_count,
sum(amount) as total_amount,
uniq(customer_id) as unique_customers
GROUP BY date, store_id, category
)
WITH SETTINGS (
index_granularity = 1024 -- 집계 후 데이터 크기 고려
);
9. 주의사항
❌ 피해야 할 패턴
1. 너무 작은 Granularity
-- BAD: 과도한 인덱스 오버헤드
WITH SETTINGS (index_granularity = 64)
2. PRIMARY KEY 컬럼에 Projection
-- BAD: 이미 최적화된 컬럼
CREATE TABLE t (...) ORDER BY user_id;
ALTER TABLE t ADD PROJECTION redundant (
SELECT * ORDER BY user_id -- 중복!
);
3. 너무 많은 Projection
-- BAD: 5개 이상은 비효율
ADD PROJECTION p1 ...;
ADD PROJECTION p2 ...;
ADD PROJECTION p3 ...;
ADD PROJECTION p4 ...;
ADD PROJECTION p5 ...;
ADD PROJECTION p6 ...; -- 너무 많음
⚠️ 버전 호환성
ClickHouse 25.11 이하:
WITH SETTINGS문법 지원 안 됨- Projection은 원본 테이블의 granularity 상속
ClickHouse 25.12+:
WITH SETTINGS문법 지원- Projection별 독립적인 granularity 설정 가능
10. 마이그레이션 가이드
25.11 → 25.12 업그레이드 시
11. 모니터링 쿼리
Projection 사용률 확인
SELECT
query_id,
query,
ProjectionNames,
read_rows,
query_duration_ms
FROM system.query_log
WHERE type = 'QueryFinish'
AND table = 'events'
AND event_date >= today() - 1
AND ProjectionNames != []
ORDER BY event_time DESC
LIMIT 20;
Projection 크기 추적
SELECT
database,
table,
name as projection_name,
formatReadableSize(sum(bytes_on_disk)) as size,
sum(rows) as rows,
sum(marks) as marks,
groupArray(active) as active_parts
FROM system.projection_parts
WHERE database = 'production'
GROUP BY database, table, name
ORDER BY sum(bytes_on_disk) DESC;
12. 결론
핵심 원칙
- 쿼리 패턴 분석 우선: 실제 워크로드 기반 결정
- 작게 시작: 가장 빈번한 1~2개 쿼리 패턴만 최적화
- 측정 후 확장: EXPLAIN과 query_log로 효과 검증
- 균형 유지: 스토리지 vs 성능 트레이드오프 고려
Granularity 선택 요약
사용 케이스 | 권장 Granularity | 예상 성능 향상 |
Point Query (ID 조회) | 256 ~ 512 | 20x ~ 30x |
User Session 조회 | 512 ~ 1024 | 10x ~ 20x |
Category 필터 | 1024 ~ 2048 | 5x ~ 10x |
시간 범위 스캔 | 4096 ~ 8192 | 1x ~ 2x |
전체 테이블 집계 | 8192 ~ 16384 | 0.5x ~ 1x |
참고 자료
- ClickHouse Projections 공식 문서
- Release Notes 25.12
- Index Granularity 튜닝 가이드
활용 코드