본문으로 건너뛰기
clickhouse검색
핵심 구조

ClickHouse 구조 한눈에 (열 지향 저장, 벡터화 실행, 왜 빠른가)

ClickHouse가 분석 쿼리에서 빠른 이유를 열 지향 저장, 희소 기본 키 인덱스, 벡터화 실행, 머지 기반 쓰기 모델의 네 축으로 정리하고, 어떤 워크로드에 맞고 어떤 워크로드에 안 맞는지 짚습니다.

아키텍처열 지향벡터화OLAPMergeTree2026-10 기준, 약 10분

한 줄 정의

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 문제가 생깁니다.

요청이 처리되는 길

clickhouse-server (쿼리 한 개, 코어 수만큼 병렬 스트림) 저장 (테이블 하나 = 파트 여러 개, 파트 = 열 파일 + 희소 인덱스) 클라이언트TCP 9000, HTTP 8123 파서, 분석기AST, 이름과 타입 해석 플래너, 최적화인덱스 범위, PREWHERE 파이프라인 실행벡터화, 열 블록 단위 결과 직렬화JSONEachRow, Native, Parquet primary.idx8,192행마다 마크 하나 읽을 그래뉼 범위 결정파트, 파티션, 스킵 인덱스 열 파일만 읽기ts.bin, country.bin (압축 블록) 백그라운드 머지작은 파트를 큰 파트로
쿼리 하나가 처리되는 길
  1. 클라이언트가 Native TCP(9000), HTTP(8123), MySQL/PostgreSQL 호환 포트, gRPC 가운데 하나로 접속합니다.
  2. 파서가 AST를 만들고, 분석기(analyzer) 가 이름 해석과 타입 검사, 서브쿼리와 CTE 처리를 합니다. 새 분석기는 24.3부터 기본값입니다.
  3. 플래너가 쿼리 플랜을 만들고 최적화합니다. PREWHERE 이동, 조건 푸시다운, 프로젝션 선택, 인덱스 활용 범위 계산이 여기서 결정됩니다.
  4. 파이프라인이 파트별, 그래뉼 범위별로 읽기 스트림을 만들어 병렬로 실행합니다.
  5. 결과는 요청한 포맷으로 직렬화되어 돌아갑니다. 서버는 압축된 열 데이터를 그대로 클라이언트에 보낼 수도 있습니다(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에 맞는지 가늠할 때 쓰는 체크리스트입니다.

  1. 쓰기 패턴: 초당 수천 행 이상을 배치나 스트림으로 넣는가, 아니면 한 행씩 넣는가. 한 행씩이면 비동기 INSERT나 버퍼가 필요합니다.
  2. 갱신 패턴: 과거 데이터를 자주 UPDATE/DELETE 하는가. 잦다면 ReplacingMergeTree나 경량 갱신으로 설계를 바꿔야 합니다.
  3. 쿼리 패턴: 필터 열이 몇 개로 고정되는가. 정렬 키를 그 열로 잡을 수 있으면 대부분 해결됩니다.
  4. 조인 패턴: 큰 테이블끼리 조인이 많은가. 가능하면 적재 시점에 비정규화하거나 딕셔너리로 바꿉니다.
  5. 일관성 요구: 복제본 사이의 지연(최종 일관성)을 허용하는가. 허용하지 않으면 select_sequential_consistency 같은 설정 비용이 듭니다.

팁 첫 PoC는 실제 데이터 하루치를 clickhouse-local이나 Cloud 무료 체험에 넣고 상위 5개 대시보드 쿼리를 그대로 돌려 보는 것이 가장 빠릅니다. 정렬 키 두세 가지를 바꿔 가며 EXPLAIN indexes = 1로 읽은 그래뉼 수를 비교하면 설계가 금방 잡힙니다.

공식 문서

이 문서에서