ClickHouse Refreshable Materialized View

ClickHouse Refreshable Materialized View

ClickHouse 분류
Core Architecture
Type
Introduction
작성자

Ken

  • 들어가며
  • ClickHouse Incremental MView의 강력함
  • 그럼에도 Incremental MView가 해결하지 못하는 문제들
  • 1. UPDATE/DELETE에 대응 불가
  • 2. 복잡한 JOIN은 사실상 불가능
  • 3. 전체 데이터 대상 순위/비율 계산 불가
  • 4. 외부 데이터 소스 주기적 동기화
  • Refreshable Materialized View: 배치 처리의 귀환
  • 핵심 동작 방식
  • 실습: Incremental vs Refreshable MView 비교
  • 시나리오: 일별 사용자-브랜드별 행동 분석
  • 모니터링: system.view_refreshes
  • RMV만이 해결할 수 있는 실제 사례들
  • 사례 1: 전체 순위 및 백분위 계산
  • 사례 2: 복잡한 다중 테이블 JOIN
  • 사례 3: 구매자만 필터링한 전체 행동 분석
  • 유용한 기능들
  • OFFSET: 정확한 실행 시간 제어
  • RANDOMIZE FOR: 부하 분산
  • DEPENDS ON: DAG 구성
  • 수동 관리 명령어
  • 향후 로드맵 및 고려사항
  • 현재 제한사항 (v25.8 기준)
  • 계획된 개선사항
  • 결론: 언제 무엇을 써야 하나?
  • 참조 링크

들어가며

ClickHouse의 Materialized View(MView)는 실시간 데이터 처리의 강력한 무기입니다. INSERT 시점에 트리거되어 데이터를 즉시 집계하는 방식은 수억 건의 데이터도 밀리초 단위로 쿼리할 수 있게 해줍니다. 하지만 모든 분석 시나리오가 이 "증분(Incremental)" 패러다임에 맞지는 않습니다.

이 글에서는 ClickHouse 23.12에서 실험적으로 도입되고 24.10에서 프로덕션 레디가 된 Refreshable Materialized View(이하 RMV)가 왜 필요한지, 기존 MView로는 해결할 수 없는 어떤 문제를 풀 수 있는지, 그리고 실제 구현 사례를 살펴보겠습니다.

ClickHouse Incremental MView의 강력함

ClickHouse의 전통적인 MView는 다른 OLTP 데이터베이스의 MView와 근본적으로 다릅니다. PostgreSQL이나 Oracle의 MView가 "주기적으로 전체 쿼리를 다시 실행"하는 방식이라면, ClickHouse MView는 INSERT 트리거처럼 동작합니다.

이 구조의 장점은 명확합니다:

  1. 실시간성: 데이터가 INSERT되는 순간 집계가 업데이트됨
  2. 효율성: 전체 데이터를 다시 읽지 않고, 새로 들어온 블록만 처리
  3. 확장성: AggregatingMergeTree와 결합하면 부분 집계 상태를 저장하여 무한히 확장 가능

특히 AggregatingMergeTree-State/-Merge 함수 조합은 ClickHouse만의 독보적인 기능입니다. uniqState(), sumState(), quantileState() 등을 사용하면 중간 집계 상태를 저장했다가 쿼리 시점에 최종 결과로 병합할 수 있습니다.

그럼에도 Incremental MView가 해결하지 못하는 문제들

강력한 Incremental MView에도 구조적 한계가 있습니다:

1. UPDATE/DELETE에 대응 불가

ClickHouse MView는 INSERT 트리거입니다. 원본 테이블의 데이터가 수정되거나 삭제되어도 MView는 알지 못합니다.

-- 주문이 취소되어 원본 테이블에서 삭제되었지만,
-- MView에 이미 집계된 매출은 그대로 남아있음
ALTER TABLE landing_purchase DELETE WHERE order_id = 'cancelled_order';
-- mv_daily_sales에는 여전히 취소된 주문의 매출이 포함됨!

ReplacingMergeTree와 조합하는 워크어라운드가 있지만, 복잡하고 완벽하지 않습니다.

2. 복잡한 JOIN은 사실상 불가능

MView는 "INSERT된 블록"만 볼 수 있습니다. LEFT 테이블에 새 데이터가 들어왔을 때, RIGHT 테이블의 전체 데이터와 JOIN하려면 매 INSERT마다 RIGHT 테이블을 풀스캔해야 합니다.

-- 이런 MView는 동작하지만, 성능이 심각하게 저하됨
CREATE MATERIALIZED VIEW mv_enriched AS
SELECT
    e.user_id,
    e.event_type,
    u.segment,  -- 사용자 프로필 테이블에서 JOIN
    u.lifetime_value
FROM events e
LEFT JOIN user_profiles u ON e.user_id = u.user_id;
-- 매 INSERT 시 user_profiles 테이블을 스캔!

3. 전체 데이터 대상 순위/비율 계산 불가

"전체 사용자 중 상위 10% 구매자"나 "전체 매출 대비 비율" 같은 계산은 증분 방식으로 처리할 수 없습니다.

-- 이런 쿼리는 매번 전체 데이터를 봐야 함
SELECT
    user_id,
    total_spend,
    total_spend / (SELECT sum(total_spend) FROM all_users) as spend_ratio,
    ntile(10) OVER (ORDER BY total_spend DESC) as decile
FROM user_spending_summary;

4. 외부 데이터 소스 주기적 동기화

MySQL, PostgreSQL, S3 등 외부 소스에서 데이터를 주기적으로 가져와야 하는 경우, Incremental MView는 적합하지 않습니다.

Refreshable Materialized View: 배치 처리의 귀환

RMV는 전통적인 "주기적 전체 갱신" 방식의 MView를 ClickHouse 네이티브로 구현한 것입니다. 핵심 문법은 다음과 같습니다:

CREATE MATERIALIZED VIEW view_name
REFRESH EVERY 1 HOUR        -- 갱신 주기
OFFSET 30 MINUTE            -- 정각 기준 오프셋 (예: 매시 30분에 실행)
RANDOMIZE FOR 5 MINUTE      -- 부하 분산을 위한 랜덤 지연
DEPENDS ON other_view       -- 의존성 (other_view 완료 후 실행)
TO target_table             -- 결과 저장 테이블
AS SELECT ...               -- 갱신 쿼리

핵심 동작 방식

  1. REPLACE 모드 (기본): 갱신 시 대상 테이블을 원자적으로 완전 교체
  2. APPEND 모드: 기존 데이터를 유지하고 새 결과를 추가 (히스토리 축적에 유용)

실습: Incremental vs Refreshable MView 비교

ClickHouse Cloud에서 실제로 두 방식을 구현하여 비교해보았습니다.

시나리오: 일별 사용자-브랜드별 행동 분석

원본 데이터는 사용자 활동 로그(view_item_list, select_item, add_to_cart, purchase)입니다.

Incremental MView 구현:

Refreshable MView 구현:

모니터링: system.view_refreshes

RMV의 상태는 system.view_refreshes 테이블에서 확인할 수 있습니다:

SELECT
    database,
    view,
    status,
    last_success_time,
    next_refresh_time,
    read_rows,
    written_rows
FROM system.view_refreshes;

실제 실행 결과:

database
view
status
last_success_time
next_refresh_time
e-commerce
rmv_daily_user_summary
Scheduled
2025-11-29 13:42:51
2025-11-29 16:00:00
e-commerce
rmv_daily_category_summary
Scheduled
2025-11-29 13:42:44
2025-11-29 16:00:00
e-commerce
rmv_daily_brand_summary
Scheduled
2025-11-29 13:42:37
2025-11-29 16:00:00

RMV만이 해결할 수 있는 실제 사례들

사례 1: 전체 순위 및 백분위 계산

NYC 택시 데이터(2천만 건)에서 가장 인기 있는 노선 TOP 20을 분석하는 경우:

사례 2: 복잡한 다중 테이블 JOIN

사례 3: 구매자만 필터링한 전체 행동 분석

"구매한 사용자"의 구매 이전 전체 행동을 분석해야 하는 경우:

이 쿼리는 HAVING 절에서 전체 그룹을 평가해야 하므로, 증분 방식으로는 처리할 수 없습니다.

유용한 기능들

OFFSET: 정확한 실행 시간 제어

-- 매일 새벽 3시(KST)에 실행 (UTC 기준 18시)
REFRESH EVERY 1 DAY OFFSET 18 HOUR

-- 매주 수요일 새벽 3시에 실행
REFRESH EVERY 1 WEEK OFFSET 2 DAY 18 HOUR  -- 일요일 기준 +2일 = 화요일 밤 = 수요일 새벽

-- 30분마다, 매시 15분과 45분에 실행
REFRESH EVERY 30 MINUTE OFFSET 15 MINUTE

RANDOMIZE FOR: 부하 분산

-- 여러 RMV가 동시에 실행되지 않도록 ±5분 랜덤 지연
REFRESH EVERY 1 HOUR RANDOMIZE FOR 10 MINUTE

DEPENDS ON: DAG 구성

이렇게 하면 dbt나 Airflow 없이도 ClickHouse 내에서 간단한 데이터 파이프라인을 구성할 수 있습니다.

수동 관리 명령어

-- 즉시 갱신 트리거
SYSTEM REFRESH VIEW database.view_name;

-- 스케줄 중지/시작
SYSTEM STOP VIEW database.view_name;
SYSTEM START VIEW database.view_name;

-- 갱신 취소
SYSTEM CANCEL VIEW database.view_name;

-- 갱신 완료까지 대기
SYSTEM WAIT VIEW database.view_name;

향후 로드맵 및 고려사항

현재 제한사항 (v25.8 기준)

  1. Replicated 환경에서의 조율: Replicated 데이터베이스에서는 Keeper를 통해 레플리카 간 조율이 이루어지며, 한 번에 하나의 레플리카만 갱신을 수행
  2. 증분 갱신 미지원: RMV는 항상 전체 쿼리를 실행 (증분 최적화는 아직 없음)
  3. APPEND 모드의 데이터 누적: APPEND 모드 사용 시 대상 테이블 관리 전략 필요

계획된 개선사항

GitHub Issue #33919과 관련 PR들에 따르면:

  1. 동시성 제어: 명명된 "concurrency group" 내에서 동시 실행 수 제한
  2. 우선순위 설정: 리소스 경합 시 어떤 뷰를 먼저 실행할지 제어
  3. 부분 갱신: "매일 최근 3일치 파티션만 교체" 같은 증분 패턴 지원
  4. 백업 보관: 갱신 후 이전 테이블을 N개까지 보관하는 옵션

결론: 언제 무엇을 써야 하나?

기준
Incremental MView
Refreshable MView
데이터 특성
Append-only, 수정/삭제 없음
UPDATE/DELETE 발생 가능
쿼리 복잡도
단순 집계, 필터링
복잡한 JOIN, 윈도우 함수
실시간 요구
밀리초~초 단위 반영 필요
분~시간 단위 지연 허용
전체 데이터 참조
불필요
순위, 비율 등 전체 참조 필요
외부 데이터
불필요
주기적 외부 동기화 필요

ClickHouse의 강력함은 두 가지 패러다임을 함께 활용할 때 극대화됩니다:

  1. 실시간 경로: Landing 테이블 → Incremental MView → Pre-aggregation 테이블
  2. 배치 경로: Pre-aggregation 테이블 → Refreshable MView → 분석용 요약 테이블

두 방식을 적절히 조합하면, 실시간성과 분석 유연성을 모두 확보할 수 있습니다.

참조 링크

공식 문서

  • Refreshable Materialized View 공식 문서 - RMV의 개념, 문법, 활용 사례를 상세히 설명
  • CREATE VIEW 문법 레퍼런스 - REFRESH, OFFSET, DEPENDS ON 등 전체 문법 정의
  • Incremental Materialized View 공식 문서 - 전통적인 ClickHouse MView의 동작 원리

릴리즈 노트 및 개발 히스토리

  • ClickHouse 23.12 릴리즈 노트 - RMV가 실험적 기능으로 최초 도입된 버전
  • GitHub Issue #33919: Refreshable Materialized Views - RMV 기능 제안 및 로드맵 논의
  • GitHub PR #58934: APPEND 모드 및 개선사항 - APPEND 모드, 재시도 로직 등 주요 개선사항

관련 블로그 및 기술 글

  • ClickHouse October 2024 Newsletter - 24.9에서 도입된 APPEND 절 소개
  • Incremental vs Refreshable MView 비교 (Medium) - 두 방식의 차이점과 사용 시나리오

이 글은 ClickHouse Cloud와 MCP를 활용한 실습을 기반으로 작성되었습니다.