콘텐츠로 이동

[참고] ClickHouse 구조의 이해

dbt에서 ClickHouse를 사용할 때 알아야 할 핵심 아키텍처 개념을 정리합니다.


1. ClickHouse란?

"엄청 많은 데이터를 엄청 빠르게 읽는 데 특화된 DB"

일반적인 서비스용 RDB와 ClickHouse는 목적 자체가 다릅니다.

구분 일반 RDB (PostgreSQL, MySQL) ClickHouse
저장 방식 Row 기반 (행 단위 저장) Column 기반 (열 단위 저장)
강점 쓰기 (INSERT/UPDATE/DELETE) 읽기 (SELECT, 집계)
주 용도 서비스 DB (사용자 요청 처리) 분석 DB (대규모 데이터 집계)
비유 편의점 (소량 다품종, 빠른 출입) 대형 창고 (대량 보관, 한번에 꺼내기)
행 수정 빠름 (개별 행 접근 최적화) 느림 (컬럼 단위라 행 수정 비효율)
집계 쿼리 느림 (모든 컬럼 읽어야 함) 빠름 (필요 컬럼만 읽음)

왜 dbt + ClickHouse인가?

dbt는 SELECT 기반 변환 도구이고, ClickHouse는 SELECT(읽기)에 최적화된 DB입니다. "읽기 특화 도구 + 읽기 특화 DB" 조합이라 궁합이 좋습니다.


2. ClickHouse 클러스터 구조

ClickHouse는 단일 서버로도 빠르지만, 대규모 데이터를 처리할 때는 클러스터로 구성합니다.

핵심 개념

개념 설명
Shard (샤드) 데이터를 여러 노드에 나눠 저장 (수평 확장)
Replica (레플리카) 같은 데이터를 여러 노드에 복제 (고가용성)
Distributed Table 모든 샤드를 묶어서 조회하는 가상 테이블

클러스터 구조 한눈에 보기

핵심 개념 정리

  • Node = 독립된 물리 서버 1대. 각각 다른 위치에 있을 수 있음. 장애는 Node 단위로 발생.
  • Shard = "같은 파티션의 데이터를 담당하는 Node 그룹"이라는 논리적 설정. 물리적으로 묶인 게 아님!
  • Replica = 같은 Shard 내 Node끼리 동일한 데이터를 자동 복제. Node 1이 죽으면 같은 Shard의 Node 2가 대신 응답.
  • Distributed Table = 서로 다른 Shard에서 Node 하나씩 골라 조합 → 완전한 테이블을 만들어주는 가상 테이블.
ClickHouse 클러스터 — 4개의 독립 Node
🖥️ Node 1
Shard 1Replica A
짝수 데이터 보유
🖥️ Node 2
Shard 1Replica B
짝수 데이터 보유 (Node 1 복제본)
🖥️ Node 3
Shard 2Replica A
홀수 데이터 보유
🖥️ Node 4
Shard 2Replica B
홀수 데이터 보유 (Node 3 복제본)
🔀 Distributed Table (users_distributed)
Node 1(또는 2) + Node 3(또는 4) 를 조합 → 완전한 테이블
시나리오 선택:

클러스터 동작 데모

다음 ▶ 버튼으로 INSERT 데이터 분배와 SELECT 조회 과정을 단계별로 확인하세요.

ClickHouse 클러스터 동작
Client
Shard 1 (짝수 user_id)
Replica 1-A, Replica 1-B (자동 동기화)
Shard 2 (홀수 user_id)
Replica 2-A, Replica 2-B (자동 동기화)
Distributed Table (쿼리 결과)

Shard Key란?

어떤 데이터가 어느 샤드로 갈지 결정하는 기준입니다. 위 예시에서는 user_id % 2를 사용하여 짝수는 Shard 1, 홀수는 Shard 2로 보냅니다.


3. Local vs Distributed 테이블

ClickHouse에서 "테이블"이라고 불리는 것이 실제로는 3가지 역할로 나뉩니다.

종류 역할 데이터 보유 설명
Local Table (_local) 실제 데이터 저장소 O 각 Node에 실제 데이터가 저장됨
Distributed Table (_distributed) 라우터 X 모든 Node에 쿼리를 분산하는 가상 테이블
View (FINAL) 중복 제거 래퍼 X SELECT ... FROM _distributed FINAL을 감싼 뷰. 중복 제거까지 보장

View (FINAL)이란?

ClickHouse의 ReplicatedReplacingMergeTree 등은 데이터 merge 전까지 중복 행이 일시적으로 존재할 수 있습니다. FINAL을 붙이면 이 중복을 제거한 최종 결과를 보장합니다.

-- View 정의 예시
CREATE VIEW events_view AS
SELECT * FROM events_distributed FINAL

Local vs Distributed 조회 데모

같은 데이터를 Local / Distributed로 조회하면 결과가 어떻게 달라지는지 확인하세요.

Local vs Distributed 테이블 조회
Query
Node 1 (Shard 1 · 짝수)
Node 3 (Shard 2 · 홀수)
조회 결과

Local 테이블 직접 조회 주의

Local 테이블을 직접 조회하면 해당 샤드의 데이터만 보입니다. 전체 데이터가 필요하면 반드시 Distributed 테이블이나 View를 통해 조회하세요.


4. ClickHouse Materialized View의 두 종류

ClickHouse의 MV는 일반 DB의 MV와 다릅니다. 그리고 ClickHouse 내에서도 2가지 종류가 있습니다.

Incremental MV (전통적 방식)

CREATE MATERIALIZED VIEW mv_daily_users
ENGINE = SummingMergeTree()
ORDER BY event_date
AS
SELECT event_date, count() AS user_count
FROM events_local  -- Local 테이블을 소스로 지정
GROUP BY event_date
  • Source 테이블에 INSERT가 발생할 때 자동 트리거
  • 새로 들어온 배치(batch)만 처리 (과거 데이터 재처리 안 함)
  • JOIN, Window 함수 등 사용 불가 (배치 단위이므로)
  • dbt의 materialized='materialized_view'가 이 방식

Refreshable MV (ClickHouse 23.4+)

CREATE MATERIALIZED VIEW mv_daily_summary
REFRESH EVERY 1 HOUR
ENGINE = MergeTree()
ORDER BY event_date
AS
SELECT event_date,
       count() AS total_events,
       uniq(user_id) AS unique_users
FROM events_distributed FINAL  -- Distributed + FINAL 사용 가능
GROUP BY event_date
  • ClickHouse 스케줄러가 주기적으로 전체 재계산
  • JOIN, FINAL, Window 함수 등 자유롭게 사용 가능
  • Airflow 없이 ClickHouse 자체적으로 스케줄링
  • dbt에서 직접 지원하지 않음 (raw SQL 필요)

두 MV 비교

구분 Incremental MV Refreshable MV
트리거 방식 INSERT 이벤트 시간 스케줄
처리 범위 새 배치만 전체 재계산
JOIN 지원 X O
FINAL 지원 X O
Window 함수 X O
dbt 지원 materialized_view 미지원 (raw SQL)
Airflow 필요 불필요 불필요
적합한 경우 단순 집계, 실시간 복잡한 변환, 주기적

두 MV 동작 비교 데모

케이스를 선택하고, 다음 ▶ 버튼으로 두 MV가 어떻게 다르게 반응하는지 비교해보세요.

Incremental MV vs Refreshable MV
Source
Incremental MV
INSERT 시 즉시 트리거 · 새 배치만 처리
Refreshable MV
주기적 전체 재계산 · JOIN/FINAL 가능

dbt에서 MV를 쓸 때 주의

dbt의 materialized='materialized_view'Incremental MV만 생성합니다. Refreshable MV는 dbt에서 지원하지 않으므로, 필요하면 raw SQL로 직접 생성해야 합니다.

복잡한 변환(JOIN, FINAL, Window 함수)이 필요하다면 → MV 대신 dbt의 incremental 모델을 사용하세요.

어떤 MV를 써야 할까?

  • 단순 집계 (count, sum 등) → Incremental MV (dbt materialized_view)
  • JOIN이나 FINAL이 필요한 복잡한 변환 → Refreshable MV (raw SQL) 또는 dbt incremental 모델

← Materialization 심화로 돌아가기

댓글