| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- NextJS
- HTTP
- version-control
- Performance
- Security
- react
- DevOps
- reliability
- backend
- 성능
- web
- aws
- Kubernetes
- architecture
- auth
- CSS
- PostgreSQL
- Git
- Debugging
- frontend
- API
- CI
- Ops
- SRE
- observability
- JavaScript
- Microservices
- database
- Infra
- Operations
- Today
- Total
고민보단 실천을
ClickHouse 성능/특성 정리: 컬럼형+MergeTree(ORDER BY/partition)로 OLAP 성능 만들기 본문
ClickHouse 성능/특성 정리: 컬럼형+MergeTree(ORDER BY/partition)로 OLAP 성능 만들기
이 글은 ClickHouse를 성능/운영 관점에서 길게 정리한 개인 노트입니다. 목표는 "지금 느린 이유"를 증거로 좁히고, 재발을 줄이는 체크리스트를 갖추는 것입니다.
전제: 성능은 DB만으로 결정되지 않습니다. 쿼리 패턴, 데이터 분포, 인덱스/스키마, 애플리케이션 트랜잭션 경계, 인프라(IO/네트워크)가 함께 결정합니다. 그래서 이 글은 '성능 모델(어디서 시간이 쓰이나) -> 설계/쿼리 -> 설정 -> 운영' 순서로 정리합니다.
이 DB를 언제 선택하나
정답은 항상 케이스 바이 케이스지만, 선택을 빠르게 만들기 위해 기준을 먼저 둡니다. 아래에 해당이 많을수록 이 DB를 고려할 만합니다.
- 대용량 로그/이벤트를 빠르게 집계/분석해야 할 때
- OLAP 성격의 쿼리가 많고 데이터량이 빠르게 성장하는 경우
- append 중심 데이터 모델이며 업데이트/삭제를 제한적으로 감당할 수 있을 때
성능 모델: 어디서 시간이 쓰이나
ClickHouse의 강점은 컬럼 지향 저장 덕분에 필요한 컬럼만 읽고 압축을 풀며 병렬로 스캔한다는 점입니다. 그래서 SELECT 컬럼 수와 WHERE 조건이 읽는 데이터 양을 직접 줄여줍니다.
MergeTree 계열 엔진에서는 데이터가 part로 쌓이고 백그라운드에서 merge가 일어납니다. 쓰기/머지/쿼리가 같은 디스크를 공유하므로 merge backlog가 쌓이면 지연이 흔들릴 수 있습니다.
핵심 용어 빠른 사전
성능/장애 분석에서 자주 쓰는 단어는 팀마다 다르게 이해되기 쉽습니다. 용어를 짧게 정의하고, 어떤 지표/증상과 연결되는지 같이 적어둡니다.
| 용어 | 설명 | 실무에서 연결되는 것 |
|---|---|---|
part | 데이터가 쌓이는 단위 파일/조각 | part가 많으면 merge backlog/메타 오버헤드 증가 |
merge | part를 병합해 최적화 | merge가 밀리면 읽기/디스크가 같이 악화 |
ORDER BY | 정렬 키(프라이머리 인덱스 유사) | 필터/범위 조회의 data skipping 효율을 좌우 |
PARTITION BY | 파티션 단위 분할 | 드롭/TTL/스캔 범위에 영향 |
data skipping | 필요 없는 블록을 건너뛰는 최적화 | 정렬/파티션 키 설계가 좋으면 스캔량이 급감 |
옵션/핵심 요소(3~6)
자주 부딪히는 핵심 요소를 표로 먼저 고정해 두면, 장애/튜닝 때 팀 커뮤니케이션이 빨라집니다.
| 항목 | 의미 | 언제 쓰는지(실무 상황) |
|---|---|---|
MergeTree | 기본 스토리지 엔진 | 대부분의 로그/이벤트 테이블 기본 선택 |
ORDER BY | 정렬 키(프라이머리 인덱스 유사) | 필터/범위 조회 성능을 좌우할 때 |
PARTITION BY | 파티션 분할 | 데이터 관리(TTL/드롭)와 스캔 범위를 줄일 때 |
Merge | 파트 병합 | 쓰기/성능 지속 가능성을 결정할 때 |
TTL | 자동 만료/이동 | 로그 보관 정책을 데이터 레벨로 관리할 때 |
실무 튜닝 포인트(설정/옵션별)
설정에는 정답이 없습니다. 워크로드(읽기/쓰기/동시성/데이터 크기) 기준으로 관측하고 조정합니다. 아래는 실무에서 가장 자주 만지는 레버를 '항목별'로 정리한 것입니다.
ORDER BY
의미: 정렬 키 선택
언제 만지나: 자주 필터링/그룹핑하는 키에 맞출 때
주의/트레이드오프: 잘못 고르면 스캔 범위가 커지고 압축 효율도 떨어질 수 있음
PARTITION BY
의미: 파티션 키 선택
언제 만지나: 기간별 드롭/TTL/백업 단위를 만들 때
주의/트레이드오프: 파티션이 너무 많으면 오버헤드 증가
index_granularity
의미: 인덱스 그라뉼러리티
언제 만지나: 스캔/필터 성능을 미세 조정할 때
주의/트레이드오프: 너무 작게 잡으면 인덱스 오버헤드 증가
max_memory_usage
의미: 쿼리 메모리 상한
언제 만지나: 폭주 쿼리를 제한할 때
주의/트레이드오프: 너무 낮으면 정상 쿼리도 실패
max_bytes_before_external_group_by
의미: group by spill 임계치
언제 만지나: 대형 집계에서 OOM을 피할 때
주의/트레이드오프: spill 되면 디스크 IO로 지연 증가
background_pool_size
의미: 백그라운드 작업 스레드
언제 만지나: merge backlog가 심각할 때
주의/트레이드오프: IO 병목이면 스레드만 늘려도 해결 안 됨
max_threads
의미: 쿼리 병렬 스레드
언제 만지나: CPU를 더 써서 빠르게 처리하고 싶을 때
주의/트레이드오프: 동시 쿼리 환경에서는 전체 QoS 고려
async_insert
의미: 비동기 insert
언제 만지나: 대량 ingest에서 지연을 완화할 때
주의/트레이드오프: 내구성/가시성 요구와 충돌할 수 있음
쿼리/스키마 설계에서 성능이 갈리는 지점
ClickHouse 설계에서 가장 중요한 건 ORDER BY입니다. 이는 data skipping 효율을 결정하고 압축 효율에도 영향을 줍니다. 조회에서 자주 쓰는 필터/범위 조건이 무엇인지 먼저 고정해야 합니다.
파티션은 운영 단위입니다. 보통 날짜/월 단위로 파티셔닝해 TTL/드롭을 쉽게 하고 스캔도 줄입니다. 하지만 너무 잘게 쪼개면 part/메타데이터가 늘어 오히려 느려질 수 있습니다.
CREATE TABLE events (
ts DateTime,
tenant_id UInt32,
user_id UInt64,
event_type String,
payload String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (tenant_id, ts);SELECT tenant_id, event_type, count() FROM events
WHERE tenant_id = 10 AND ts >= now() - INTERVAL 7 DAY
GROUP BY tenant_id, event_type
ORDER BY count() DESC
LIMIT 20;진단 명령/쿼리 모음(바로 실행용)
장애 때는 문서 찾을 시간이 없습니다. 아래는 현장에서 자주 쓰는 '첫 5분' 진단 명령/쿼리만 모은 것입니다. 환경/권한에 따라 일부는 제한될 수 있습니다.
-- 실행 중/최근 쿼리 로그(환경에 따라 system.query_log 사용)
SELECT type, query_duration_ms, read_rows, read_bytes, written_rows, written_bytes, query FROM system.query_log ORDER BY event_time DESC LIMIT 20;
-- part 상태
SELECT table, count() AS parts, sum(rows) AS rows, sum(bytes_on_disk) AS bytes FROM system.parts WHERE active GROUP BY table ORDER BY parts DESC LIMIT 20;
-- merge 진행 상태
SELECT * FROM system.merges ORDER BY elapsed DESC LIMIT 20;자주 하는 실수(운영/튜닝)
실수 체크리스트:
- ORDER BY를 쿼리 패턴 없이 대충 정해 data skipping 효율을 망친다
- 파티션을 너무 잘게 쪼개 part 수 폭증을 만든다
- 필요 없는 컬럼까지 SELECT *로 읽어 스캔 바이트를 키운다
- merge backlog를 모니터링하지 않고, 느려지면 노드만 늘린다
- 대형 집계에서 메모리 상한 없이 OOM/kill을 반복한다
- TTL/rollup 없이 원본을 무한히 쌓아 운영을 무너뜨린다
운영/모니터링 체크리스트
운영에서는 merge backlog(파트 수), 디스크 IO/큐, 쿼리 메모리 사용, 느린 쿼리의 read bytes/rows, TTL 작업 영향을 봅니다. 느린 쿼리는 얼마나 읽었는지(bytes read)가 거의 답입니다.
공통 체크리스트:
- 느린 쿼리 top N을 지속 수집(시간/호출수/총시간)
- 지연(p95/p99)과 오류율을 애플리케이션 지표와 함께 본다
- 캐시/메모리/디스크 IO 중 무엇이 병목인지 먼저 분리한다
- 복제/HA를 쓰면 일관성 요구가 높은 기능을 분리한다
- 튜닝/변경 후에는 동일 조건으로 재측정해 회귀를 막는다
트러블슈팅 루틴(순서 고정)
장애 때는 선택지가 너무 많아서 흔히 길을 잃습니다. 아래 순서를 팀 표준으로 정해두면, 원인 추적이 빨라지고 '감으로 설정만 만지는' 일을 줄일 수 있습니다.
1) 느린 쿼리의 read bytes/rows를 본다
2) WHERE 조건이 ORDER BY 키에 얼마나 걸리는지 확인한다
3) 필요한 컬럼만 SELECT 하는지 확인한다
4) 파티션 프루닝이 되는지 확인한다
5) merge backlog/part 수와 디스크 IO를 확인한다
6) ORDER BY/PARTITION BY를 재설계(필요시 재적재)한다문제 상황(정확히 1개)
상황 -> 초기에는 빠른데 시간이 지나며 쿼리가 점점 느려지고, 디스크 사용량도 늘어난다.
원인 -> part가 너무 많이 쌓여 merge가 따라가지 못했고 쿼리가 많은 part를 스캔하게 됐다.
해결 -> merge backlog를 줄이기 위해 설정/리소스를 조정하고, 파티션/정렬 키를 재검토하며 필요하면 재적재 전략을 사용한다.
예방 팁 -> 적재량 대비 merge 여력을 산정하고 part 수/merge 시간을 지표로 운영하며 파티션을 과도하게 쪼개지 않는다.
참고/출처
정확한 동작/버전별 차이는 공식 문서를 기준으로 확인합니다. 실무에서는 버전 차이가 곧 성능/장애 차이입니다.
'DB' 카테고리의 다른 글
| MariaDB 성능/특성 정리: MySQL 호환 + 옵티마이저/복제 운영 포인트 (0) | 2026.03.07 |
|---|---|
| Elasticsearch/OpenSearch 성능/특성 정리: shards/heap/refresh/mapping으로 느림을 설명하기 (0) | 2026.03.07 |
| Amazon DynamoDB 성능/특성 정리: 키 설계로 핫 파티션/비용/스로틀링 줄이기 (0) | 2026.03.06 |
| Apache Cassandra 성능/특성 정리: partition key/모델링/tombstones/compaction (0) | 2026.03.06 |
| Amazon Aurora MySQL 성능/특성 정리: Reader 확장, Failover, Lag, connection storm 대응 (0) | 2026.03.06 |
