1. Why ClickHouse?
웹 로그 필터에서 시작한 컬럼 DB가 풀려는 문제
- Haram Lee
- 2026-10-14
- studies / Topics / ClickHouse
ClickHouse는 데이터를 계속 받아들이면서 어떻게 빠르게 읽을까?
- 들어온 데이터는 어떤 모양으로 디스크에 쌓이나?
- 최근 5분 데이터만 어떻게 골라내나?
- 남은 행은 CPU에서 어떻게 집계하나?
- 그 속도를 얻으려고 무엇을 포기했나?
1.1 The Question
웹 성능 이벤트를 모으는 파이프라인을 만들고 있다. 브라우저가 보낸 이벤트는 gateway를 거쳐 Kafka에 쌓이고, Flink가 읽어 저장소에 적재한다. 지금 저장소는 BigQuery다.
flowchart LR browser["Browser"] --> gateway["Gateway"] --> kafka["Kafka"] --> flink["Flink"] flink --> bq["BigQuery"] flink -.-> ch["ClickHouse?"]
앞단의 Kafka와 Flink는 지난 sulco 발표에서 살펴봤다. 이번에는 앞단을 그대로 두고 저장소만 바꾸면 무엇이 달라지는지가 궁금했다. 구체적인 요구는 이렇다. 최근 5분 동안의 LCP p75를 페이지별로 15초마다 계산하고, 기준을 넘으면 알림을 보내고 싶다.
BigQuery로도 이 요구를 구현할 수는 있다. 다만 실시간 분석에 자주 거론되는 ClickHouse가 같은 일을 어떤 구조로 처리하는지 알아야 둘을 제대로 비교할 수 있다. 그래서 ClickHouse 팀이 2024년 VLDB에 낸 시스템 논문을 읽었다.
1.2 Where ClickHouse Came From
ClickHouse는 2009년에 웹 스케일 로그 데이터를 거르고 집계하는 연산자로 출발했고, 2016년에 오픈소스로 공개됐다. 범용 데이터베이스가 아니라 처음부터 “큰 로그 table을 빠르게 거르고 묶는 일"을 하려고 만들었다는 점이 이후 설계 곳곳에 드러난다.
논문은 현대 분석 데이터베이스가 풀어야 할 문제를 다섯 가지로 정리한다.
- 많은 데이터가 빠르게 들어온다. 웹 분석이나 금융처럼 데이터가 끝없이 쌓이는 곳에서는 높은 수집량을 버티면서, 오래된 데이터는 집계하거나 보관 계층으로 옮겨 비중을 낮춰야 한다. 이 작업 때문에 동시에 돌고 있는 조회가 느려져서는 안 된다.
- 동시에 많은 쿼리가 낮은 지연을 기대한다. 탐색용 ad-hoc 쿼리도 있고, 대시보드처럼 주기적으로 반복되는 쿼리도 있다. 쿼리가 반복된다면 물리적 저장 구조를 그 쿼리에 맞춰 둘 수 있다.
- 데이터가 여러 곳, 여러 형식으로 흩어져 있다. 외부 시스템의 데이터를 읽고 쓸 수 있어야 한다.
- SQL로 편하게 쓰고 성능을 들여다볼 수 있어야 한다.
- 장애에 강하고 어디서나 돌아야 한다. 복제를 지원하고, 오래된 노트북부터 큰 서버까지 실행할 수 있어야 한다. JVM의 garbage collection을 피하고 SIMD 같은 하드웨어 기능을 직접 쓰려면 native binary가 유리하다.
이 글에서는 1번과 2번을 중심으로 본다. 내 요구도 정확히 그 둘이 겹치는 자리에 있다. 데이터는 계속 들어오고, 같은 집계는 15초마다 반복된다.
1.3 Architecture at a Glance
ClickHouse는 C++로 쓴 정적 링크 binary 하나다. 엔진은 크게 세 계층으로 나뉜다.
flowchart TD access["접근 계층<br/>native · HTTP · MySQL · PostgreSQL 프로토콜"] --> query["쿼리 처리 계층<br/>vectorized 실행 + LLVM 컴파일"] query --> storage["저장 계층<br/>table engine"] query --> integration["통합 계층<br/>Kafka · S3 · PostgreSQL · Iceberg ..."]
저장 계층은 table engine이라는 단위로 이루어진다. 가장 중요한 것은 MergeTree 계열이다. ClickHouse가 자기 데이터를 디스크에 저장하는 기본 형식이다. 그 밖에 dictionary처럼 조회를 빠르게 하기 위한 특수 엔진이 있고, Kafka나 S3 같은 외부 시스템을 table처럼 보이게 하는 가상 엔진이 있다.
배포 형태도 다양하다. 직접 서버를 띄우는 on-premise 모드, 관리형 서비스인 ClickHouse Cloud, 파일을 SQL로 다루는 명령행 도구인 standalone 모드(clickhouse-local), 그리고 DuckDB처럼 프로세스 안에 엔진을 넣는 chDB가 있다. 논문은 on-premise 모드를 기준으로 설명한다.
여러 노드로 늘릴 때는 sharding과 복제를 쓴다. sharding은 한 노드에 담기 어려운 크기(보통 수십 TB)의 데이터를 나눠 담으려고 쓰고, 복제는 장애에 대비하려고 쓴다. 둘은 서로 독립적이어서, shard 두 개를 각각 두 노드에 복제하는 식으로 조합한다.
1.4 One Query to Follow
이 시리즈는 쿼리 하나를 끝까지 따라간다.
SELECT page, quantile(0.75)(lcp_ms)
FROM page_events
WHERE event_time >= now() - INTERVAL 5 MINUTE
GROUP BY page;이 쿼리가 빠르려면 세 가지가 맞물려야 한다.
- 저장. 초당 수천 건씩 들어오는 이벤트가 디스크에 어떤 모양으로 쌓이는지에 따라 이후의 모든 것이 정해진다. 2장에서 MergeTree를 본다.
- 찾기. 며칠 치 데이터에서 최근 5분만 골라내야 한다. 정렬 순서와 희소 index가 이 일을 한다. 같은 2장에서 다룬다.
- 집계. 남은 행을 여러 코어에서 동시에 묶는다. 3장에서 실행 엔진을 본다.
이 속도에는 대가가 따른다. 수정과 삭제, 중복 제거, 일관성에서 무엇을 포기했는지는 4장에서 정리한다. 그리고 5장에서 처음의 웹 성능 알림 요구로 돌아와 실제로 설계하고 측정해 본다.