ClickHouse 구조 한눈에 (열 지향 저장, 벡터화 실행, 왜 빠른가)
ClickHouse가 분석 쿼리에서 빠른 이유를 열 지향 저장, 희소 기본 키 인덱스, 벡터화 실행, 머지 기반 쓰기 모델의 네 축으로 정리하고, 어떤 워크로드에 맞고 어떤 워크로드에 안 맞는지 짚습니다.
한 줄 정의
ClickHouse는 열 지향(column-oriented) OLAP 데이터베이스입니다. 수십억 행에서 몇 개 열을 골라 집계하는 쿼리를 밀리초에서 초 단위로 처리하는 데 최적화되어 있습니다. 2016년 Yandex가 오픈소스로 공개했고, 지금은 ClickHouse, Inc.가 개발을 이끌며 Apache 2.0 라이선스로 배포합니다. 셀프 호스팅(오픈소스)과 ClickHouse Cloud(관리형) 두 가지로 씁니다.
고객에게 설명할 때는 이렇게 요약합니다.
| 잘하는 것 | 잘 못하는 것 |
|---|---|
| 큰 테이블에서 소수 열을 읽어 집계, 필터, GROUP BY | 단건 UPDATE/DELETE가 잦은 트랜잭션(OLTP) |
| 초당 수십만 행 이상의 대량 INSERT | 한 행씩 꽂는 소량 INSERT (파트가 쏟아짐) |
| 시계열, 로그, 이벤트, 클릭스트림, 관측성 데이터 | 넓은 행을 통째로 읽는 포인트 조회 |
| 실시간 대시보드, 사용자 노출(user-facing) 분석 | 복잡한 다중 테이블 트랜잭션, 외래 키 제약 |
빠른 이유 네 가지
1. 열 지향 저장
행 지향 DB는 한 행의 모든 열을 붙여 저장하지만, ClickHouse는 열마다 따로 파일로 저장합니다. SELECT count() FROM events WHERE country = 'KR' 같은 쿼리는 country 열만 읽습니다. 열 하나의 값은 서로 비슷하므로 압축률도 크게 올라갑니다. 로그 데이터는 10배에서 30배, 정수 시계열은 더 높은 압축이 흔합니다. 자세한 내용은 압축과 코덱에 있습니다.
2. 희소 기본 키 인덱스와 그래뉼
데이터는 정렬 키 순서로 저장되고, 기본 키 인덱스는 8,192행마다 하나의 마크만 기록하는 희소 인덱스입니다. 인덱스가 작아서 메모리에 상주하고, 쿼리는 조건에 맞는 그래뉼(granule) 범위만 읽습니다. B-tree처럼 행 하나를 콕 찍는 구조가 아니라 "읽을 블록 범위를 줄이는" 구조라는 점이 핵심입니다. MergeTree 엔진과 정렬 키, 기본 키, 파티션에서 이어집니다.
3. 벡터화 실행과 병렬 처리
연산은 행 하나씩이 아니라 열 블록(수천 개 값) 단위로 CPU에 전달되어 SIMD 명령으로 처리됩니다. 한 쿼리는 코어 수만큼 스트림으로 나뉘어 병렬 실행되고(max_threads), 복제본이 여럿이면 병렬 복제본으로 서버 여러 대가 한 쿼리를 나눠 읽습니다. 집계는 해시 테이블 기반으로 동작하며 메모리를 넘기면 디스크로 스필할 수 있습니다.
4. 머지 기반 쓰기 모델 (LSM 유사)
INSERT는 기존 데이터를 고치지 않고 새 파트(part) 를 만듭니다. 백그라운드 머지가 파트를 합쳐 큰 파트로 만들고, 그 과정에서 정렬, 압축, 중복 제거, 집계가 일어납니다. 쓰기 경합이 없어 대량 적재가 빠르고, 대신 소량 INSERT가 잦으면 파트가 폭증하는 Too many parts 문제가 생깁니다.
요청이 처리되는 길
- 클라이언트가 Native TCP(9000), HTTP(8123), MySQL/PostgreSQL 호환 포트, gRPC 가운데 하나로 접속합니다.
- 파서가 AST를 만들고, 분석기(analyzer) 가 이름 해석과 타입 검사, 서브쿼리와 CTE 처리를 합니다. 새 분석기는 24.3부터 기본값입니다.
- 플래너가 쿼리 플랜을 만들고 최적화합니다. PREWHERE 이동, 조건 푸시다운, 프로젝션 선택, 인덱스 활용 범위 계산이 여기서 결정됩니다.
- 파이프라인이 파트별, 그래뉼 범위별로 읽기 스트림을 만들어 병렬로 실행합니다.
- 결과는 요청한 포맷으로 직렬화되어 돌아갑니다. 서버는 압축된 열 데이터를 그대로 클라이언트에 보낼 수도 있습니다(Native 포맷).
-- EXPLAIN 으로 이 길을 그대로 볼 수 있습니다
EXPLAIN indexes = 1
SELECT toStartOfHour(ts) AS h, count()
FROM events
WHERE ts >= now() - INTERVAL 1 DAY AND country = 'KR'
GROUP BY h
ORDER BY h;EXPLAIN indexes = 1 결과에서 Parts: 12/40, Granules: 310/48000처럼 전체 가운데 실제로 읽은 파트와 그래뉼 수를 확인합니다. 이 숫자를 줄이는 것이 ClickHouse 튜닝의 거의 전부입니다. 쿼리 최적화 문서에서 다룹니다.
저장 구조 요약
/var/lib/clickhouse/store/<uuid 앞 3자리>/<테이블 uuid>/
202610_1_1_0/ 파트: <파티션>_<최소블록>_<최대블록>_<머지레벨>
columns.txt 열 목록과 타입
count.txt 행 수
primary.idx 희소 기본 키 인덱스 (마크마다 키 값)
ts.bin, ts.cmrk2 열 데이터(압축 블록)와 마크 파일
country.bin, country.cmrk2
partition.dat, minmax_ts.idx
checksums.txt
202610_2_2_0/
202610_1_2_1/ 위 두 파트가 머지된 결과 (머지 레벨 1)- 작은 파트(기본 10MB 미만)는 열마다 파일을 만들지 않고 하나의
data.bin에 몰아 넣는 컴팩트(compact) 파트로 저장됩니다. 큰 파트는 와이드(wide) 파트입니다. system.parts에서 파트의 상태(active), 크기, 행 수, 압축 전후 바이트를 봅니다.
SELECT table, count() AS parts, sum(rows) AS rows,
formatReadableSize(sum(data_compressed_bytes)) AS on_disk,
formatReadableSize(sum(data_uncompressed_bytes)) AS raw,
round(sum(data_uncompressed_bytes) / sum(data_compressed_bytes), 1) AS ratio
FROM system.parts
WHERE active
GROUP BY table
ORDER BY rows DESC;서버 구성 요소
| 구성 요소 | 역할 | 비고 |
|---|---|---|
| clickhouse-server | 쿼리 처리, 저장, 머지, 복제 | 단일 바이너리. clickhouse-client, clickhouse-local도 같은 바이너리의 모드 |
| ClickHouse Keeper | 복제 메타데이터, 리더 선출, 분산 DDL 큐 | ZooKeeper 호환 프로토콜. 지금은 ZooKeeper 대신 Keeper를 권장 |
| 테이블 엔진 | 데이터를 어디에 어떻게 저장하고 읽는지 | MergeTree 가족, Log 가족, 통합 엔진(Kafka, S3, PostgreSQL 등), 특수 엔진(Distributed, Dictionary, Merge, View) |
| 딕셔너리 | 외부 키-값 데이터를 메모리에 올려 조회 | 딕셔너리 |
| system 데이터베이스 | 서버 상태, 쿼리 로그, 파트, 머지, 설정 | 모니터링과 트러블슈팅의 출발점 |
참고 clickhouse-local은 서버 없이 파일을 바로 쿼리하는 모드이고, chDB는 같은 엔진을 Python 등 프로세스 안에 임베드한 것입니다. 둘 다 같은 SQL 방언을 씁니다.
셀프 호스팅과 Cloud의 구조 차이
| 항목 | 오픈소스 (셀프 호스팅) | ClickHouse Cloud |
|---|---|---|
| 저장 엔진 | ReplicatedMergeTree, 각 복제본이 로컬 디스크에 전체 복사본 | SharedMergeTree, 오브젝트 스토리지 한 벌 위에 여러 컴퓨트 노드 |
| 복제 | 복제본끼리 파트를 가져옴(fetch) | 메타데이터만 Keeper에 공유, 데이터는 S3/GCS/Blob에 한 번 |
| 확장 | 샤드 추가와 리밸런싱을 직접 | 수직, 수평 스케일링을 콘솔이나 API로 |
| 운영 | 설정 파일, 업그레이드, 백업, Keeper 운영을 직접 | 관리형. 설정은 SQL 수준 설정과 일부 서버 설정만 노출 |
SharedMergeTree와 compute-compute 분리는 ClickHouse Cloud 개요와 컴퓨트와 스케일링에서 자세히 다룹니다.
이런 질문을 먼저 던지세요
고객 워크로드가 ClickHouse에 맞는지 가늠할 때 쓰는 체크리스트입니다.
- 쓰기 패턴: 초당 수천 행 이상을 배치나 스트림으로 넣는가, 아니면 한 행씩 넣는가. 한 행씩이면 비동기 INSERT나 버퍼가 필요합니다.
- 갱신 패턴: 과거 데이터를 자주 UPDATE/DELETE 하는가. 잦다면 ReplacingMergeTree나 경량 갱신으로 설계를 바꿔야 합니다.
- 쿼리 패턴: 필터 열이 몇 개로 고정되는가. 정렬 키를 그 열로 잡을 수 있으면 대부분 해결됩니다.
- 조인 패턴: 큰 테이블끼리 조인이 많은가. 가능하면 적재 시점에 비정규화하거나 딕셔너리로 바꿉니다.
- 일관성 요구: 복제본 사이의 지연(최종 일관성)을 허용하는가. 허용하지 않으면
select_sequential_consistency같은 설정 비용이 듭니다.
팁 첫 PoC는 실제 데이터 하루치를
clickhouse-local이나 Cloud 무료 체험에 넣고 상위 5개 대시보드 쿼리를 그대로 돌려 보는 것이 가장 빠릅니다. 정렬 키 두세 가지를 바꿔 가며EXPLAIN indexes = 1로 읽은 그래뉼 수를 비교하면 설계가 금방 잡힙니다.