materialized_view 머티리얼라이즈는 기존 원본 테이블(source table)을 대상으로 하는 SELECT여야 합니다. PostgreSQL과 달리 ClickHouse의 materialized view는 “정적”이지 않으며, 이에 대응하는 REFRESH 작업도 없습니다. 대신 삽입 트리거처럼 동작하여, 원본 테이블에 삽입된 행에 정의된 SELECT 변환을 적용한 뒤 새 행을 대상 테이블(target table)에 삽입합니다. ClickHouse에서 materialized view가 작동하는 방식에 대한 자세한 내용은 ClickHouse materialized view 문서를 참조하십시오.
일반적인 머티리얼라이즈 개념과 공통 구성(engine, order_by, partition_by 등)은 Materializations 페이지를 참조하십시오.
대상 테이블 관리 방식
materialized_view 머티리얼라이즈를 사용하면 dbt-clickhouse는 변환된 행이 삽입될 materialized view와 대상 테이블을 모두 생성해야 합니다. 대상 테이블을 관리하는 방법은 두 가지입니다.
선택한 방식에 따라 스키마 변경, full refresh, 다중 MV 구성을 처리하는 방법이 달라집니다. 다음 섹션에서는 각 방식을 자세히 설명합니다.
암시적 대상으로 머티리얼라이즈
materialized_view 모델을 정의하면 어댑터는 다음을 수행합니다.
- 모델 이름으로 대상 테이블을 생성합니다
- 이름이
<model_name>_mv인 ClickHouse materialized view를 생성합니다
SELECT 문에 있는 컬럼을 기반으로 자동 추론됩니다. 모든 리소스(대상 테이블 + MV)는 동일한 모델 구성을 공유합니다.
여러 materialized view
UNION을 구성하고, 각 materialized view의 SQL을 --my_mv_name:begin 및 --my_mv_name:end 형식의 주석으로 감싸면 됩니다.
예를 들어, 다음은 모두 모델의 동일한 대상 테이블에 데이터를 기록하는 두 개의 materialized view를 생성합니다. materialized view의 이름은 <model_name>_mv1 및 <model_name>_mv2 형식을 따릅니다:
대상 테이블 스키마를 갱신하는 방법
dbt run이 MV의 SQL에서 기존과 다른 컬럼을 감지할 때 대상 테이블 스키마를 어떻게 갱신할지 제어할 수 있습니다.
ignore 설정값). 하지만 이 설정을 변경하면 증분 모델에서 on_schema_change 구성과 동일하게 동작하도록 할 수 있습니다.
또한 이 설정은 안전장치로도 사용할 수 있습니다. 값을 fail로 설정하면 MV의 SQL에 있는 컬럼이 첫 번째 dbt run으로 생성된 대상 테이블과 다를 때 빌드가 실패합니다.
과거 데이터 채우기
catchup=True). 이 동작은 catchup 구성 값을 False로 설정하여 비활성화할 수 있습니다.
명시적 대상 테이블을 사용하는 머티리얼라이즈(베타)
- 모든 리소스(대상 테이블 + MV)는 동일한 구성을 공유합니다. 여러 MV가 동일한 대상 테이블을 가리키는 경우
UNION ALL구문을 사용해 함께 정의해야 합니다. - 이러한 리소스는 각각 별도로 관리할 수 없으며, 모두 동일한 model 파일에서 관리해야 합니다.
- 각 MV의 이름을 쉽게 제어할 수 없습니다.
- 모든 설정을 대상 테이블과 MV가 공유하므로 각 리소스를 개별적으로 구성하기 어렵고, 어떤 구성이 어느 리소스에 속하는지 파악하기도 어렵습니다.
table 머티리얼라이즈로 별도로 정의한 다음, materialized view model에서 이를 참조할 수 있습니다.
장점
- 완전히 분리된 리소스: 이제 각 리소스를 개별적으로 정의할 수 있어 가독성이 향상됩니다.
- dbt와 CH 간 1:1 리소스 대응: 이제 dbt 도구를 사용해 각각을 개별적으로 관리하고 수정해 나갈 수 있습니다.
- 서로 다른 구성 사용 가능: 이제 각 리소스에 서로 다른 구성을 적용할 수 있습니다.
- 더 이상 명명 규칙을 지킬 필요 없음: 이제 모든 리소스는 MV에
_mv를 덧붙인 사용자 지정 이름이 아니라, 지정한 이름으로 생성됩니다.
제한 사항
- 대상 테이블 정의는 dbt 방식과는 잘 맞지 않습니다. 원본 테이블에서 읽어 오는 SQL이 아니므로 여기서는 dbt 검증을 받을 수 없습니다. MV의 SQL은 계속 dbt 유틸리티로 검증되며, 대상 테이블 컬럼과의 호환성은 CH 수준에서 검증됩니다.
ref()함수의 제약과 관련해 몇 가지 문제를 확인했습니다: 모델 간 참조를 위해 이 함수를 사용해야 하지만, 업스트림 모델만 참조할 수 있고 다운스트림 모델은 참조할 수 없습니다. 이로 인해 이 구현에는 몇 가지 문제가 있습니다. dbt-core 리포지토리에 이슈를 등록했으며, 현재 가능한 해결책을 찾기 위해 논의 중입니다(dbt-labs/dbt-core#12319):ref()를 config 블록 안에서 호출하면 공유된 모델이 아니라 현재 모델을 반환합니다. 그래서 이를 config() 섹션에 정의할 수 없고, 이 의존성을 추가하려면 주석을 사용해야 합니다. 이 부분은 dbt 문서의 the “—depends_on:” approach에서 제시하는 동일한 패턴을 따르고 있습니다.ref()는 대상 테이블이 먼저 생성되도록 강제하므로 이 용도에는 동작하지만, 생성된 문서의 의존성 차트에서는 대상 테이블이 다운스트림이 아니라 또 다른 업스트림 의존성으로 표시되어 다소 이해하기 어렵습니다.unit-test역시 실제로는 읽지 않을 대상 테이블에 대해서도 일부 데이터를 정의하도록 강제합니다. 우회 방법은 해당 테이블의 데이터를 비워 두는 것입니다.
사용
events_daily.sql:
{{ materialization_target_table(ref('events_daily')) }} macro 호출은 MV의 대상 테이블을 구성합니다.
모델 page_events_aggregator.sql:
mobile_events_aggregator.sql 모델:
구성 옵션
materialized='table')에서:
materialized view(
materialized='materialized_view')에서:
일반적으로는 MV에서
catchup만 True로 설정하거나, 해당 대상 테이블에서 repopulate_from_mvs_on_full_refresh만 True로 설정하는 것이 좋습니다. 둘 다 True로 설정하면 데이터가 중복될 수 있습니다.일반 작업
명시적 대상을 사용하는 전체 갱신
--full-refresh를 사용하면 명시적 대상 테이블이 다시 생성됩니다(따라서 이 과정에서 수집이 진행되면 데이터가 손실될 수 있습니다). 동작 방식은 구성에 따라 달라집니다.
옵션 1: 기본 --full-refresh 동작. 모든 항목이 다시 생성되지만, MV를 재생성하는 동안 대상 테이블은 비어 있거나 일부만 로드된 상태일 수 있습니다.
모든 항목을 삭제한 뒤 다시 생성합니다. MV SQL을 사용해 데이터를 다시 삽입하려면 catchup=True 설정을 유지하십시오:
catchup=False를 설정한 다음 MV에 대해 dbt run 또는 dbt run --full-refresh를 실행할 수 있습니다. 대상 테이블에서 --full-refresh를 실행하기 전에 MV가 먼저 생성되었는지 확인하십시오. 이 과정에서는 ClickHouse의 MV 정의를 사용합니다.
대상 테이블 모델에 repopulate_from_mvs_on_full_refresh=True를 설정하십시오. dbt run --full-refresh를 실행하면 다음이 수행됩니다.
- 새 임시 테이블을 생성합니다
- 각 MV의 SQL을 사용해 INSERT-SELECT를 실행합니다
- 테이블을 원자적으로 스왑합니다
대상 테이블 변경
--full-refresh 없이 MV의 대상 테이블은 변경할 수 없습니다. materialization_target_table() 참조를 변경한 뒤 일반 dbt run을 실행하면, 대상이 변경되었다는 오류 메시지와 함께 빌드가 실패합니다.
대상 테이블을 변경하려면 다음을 수행하세요:
materialization_target_table()호출을 업데이트합니다dbt run --full-refresh -s your_mv_model을 실행합니다
자주 발생하는 문제 해결
run 실행 중/실행 후 대상 테이블(target table)이 비어 있습니다
- materialized view가
catchup=False로 구성되어 있거나 대상 테이블이repopulate_from_mvs_on_full_refresh=False로 구성되어 있으면, materialized view가 생성되거나 대상 테이블이 다시 생성될 때 백필이 수행되지 않습니다. 이는 정상적인 동작입니다. 따라서 materialized view SQL을 사용해 데이터를 다시 삽입하려면 materialized view에서catchup=True(기본값)로 설정하거나 대상 테이블에서repopulate_from_mvs_on_full_refresh=True로 설정해야 합니다. 중복을 방지하려면 두 옵션을 동시에 활성화하지 마십시오. 자세한 내용은 구성 섹션을 참조하십시오. dbt run --full-refresh실행 중 materialized view가 기본값인catchup=True를 사용하면 대상이 다시 생성되고, MV가 데이터를 순차적으로 다시 삽입합니다. 이 상황을 방지하려면 명시적 대상을 사용하는 Full refresh를 참조하십시오.
dbt run --full-refresh는 repopulate_from_mvs_on_full_refresh=True가 설정된 대상 테이블에서 현재 프로젝트의 SQL이 아니라 이전 materialized view 버전의 로직을 사용합니다
repopulate_from_mvs_on_full_refresh=True는 ClickHouse에 이미 정의된 기존 MV SQL을 사용합니다. 새 materialized view 정의를 사용하게 하려면 대상 테이블에서 dbt run --full-refresh를 실행하기 전에 각 materialized view에 대해 dbt run을 실행하십시오.
run 실행 후 중복 데이터가 발생합니다
- materialized view에서
catchup=True가 활성화되어 있고, 대상 테이블에서도repopulate_from_mvs_on_full_refresh=True가 활성화되어 있을 수 있습니다. 실행하려는 작업에 따라 둘 중 하나만 사용하십시오. 자세한 내용은 구성 섹션을 확인하십시오. - 대상 테이블이
WHERE 0으로 정의되지 않았습니다. 대상 테이블은 비어 있는 상태로 생성되어야 하지만WHERE 0이 포함되지 않으면 내부 쿼리가 데이터를 삽입할 수 있습니다. 해당 절이 포함되어 있는지 확인하십시오.
dbt run --full-refresh 실행 후 활성 수집 중 데이터 손실
dbt run --full-refresh를 실행한 후 원본 테이블의 일부 행이 대상 테이블에 누락될 수 있습니다.
ClickHouse materialized view는 삽입 트리거처럼 동작하므로, 존재하는 동안에만 데이터를 포착합니다. full refresh 중에는 MV가 삭제되었다가 다시 생성되는 짧은 구간(“blind window”)이 발생합니다. 이 구간 동안 원본 테이블에 삽입된 행은 포착되지 않습니다. 자세한 내용은 활성 수집 중 동작 섹션을 확인하십시오.
디버깅 방법
ClickHouse에서 MV의 현재 대상 확인
system.tables를 쿼리합니다:
dbt가 테이블을 materialized view 대상 테이블로 인식하는지 확인
Table <table_name> is used as a target by a dbt-managed materialized view. Defaulting mv_on_schema_change to “fail” to prevent data loss.
이 메시지가 표시되면 dbt가 해당 테이블을 하나 이상의 dbt 관리 materialized view가 대상으로 사용하는 것으로 감지한 것입니다. 이 메시지가 표시되어야 하는데 보이지 않는다면, 다음 사항을 확인하십시오:
- materialized view model에서
{{ materialization_target_table(ref('your_target')) }}를 올바르게 정의했는지 - materialized view model의 구성에
materialized='materialized_view'가 포함되어 있는지 - materialized view와 대상 테이블이 모두 최소 1회 이상 실행되었는지
암시적 대상 방식에서 명시적 대상 방식으로 마이그레이션
materialized='table' 새 모델 파일을 만드십시오. 빈 테이블을 생성하려면 WHERE 0 절을 사용하십시오. 현재 암시적 대상 materialized view 모델과 동일한 이름을 사용하십시오. 이제 이 모델을 사용해 대상 테이블을 반복적으로 수정할 수 있습니다.
materialization_target_table() macro 호출을 포함하는 새 model을 생성합니다. 이전에 UNION ALL을 사용했다면 해당 부분과 comment를 제거합니다.
model 이름은 다음 명명 규칙을 따라야 합니다:
- MV가 하나만 정의된 경우 이름은
<old_model_name>_mv입니다 - 여러 MV가 정의된 경우 각각의 이름은
<old_model_name>_mv_<name_in_comments>입니다
my_model.sql 예시(암시적 대상, UNION ALL이 포함된 단일 model):
암시적 대상 방식과 명시적 대상 방식의 동작 비교
전반적인 동작 방식
수집이 진행 중일 때의 동작
- ClickHouse materialized view는 insert trigger로 동작하므로, 존재하는 동안에만 데이터를 포착합니다. materialized view가 삭제되었다가 다시 생성되면(예:
--full-refresh중) 그 사이 원본 테이블에 삽입된 행은 materialized view에서 처리되지 않습니다. 이를 materialized view가 “blind” 상태라고 합니다. - 각
catchup프로세스는 모두 materialized view SQL을 사용하는INSERT INTO ... SELECT작업을 기반으로 하며, materialized view의 동작 방식과는 별개입니다.INSERT가 시작된 후 새로 들어오는 데이터는 여기에 포착되지 않지만, attached 상태인 materialized view에는 포착됩니다.
암시적 대상 작업
명시적 대상 작업
대상 테이블 모델:
갱신 가능 materialized view
refreshable 구성 객체를 추가하십시오:
암시적 대상 예시
대상을 명시적으로 지정하는 예시
제한 사항
- 종속성이 있는 갱신 가능 materialized view(MV)를 ClickHouse에서 생성할 때, 생성 시점에 지정한 종속성이 존재하지 않더라도 ClickHouse는 오류를 발생시키지 않습니다. 대신 갱신 가능 MV는 비활성 상태로 유지되며, 종속성이 충족되어 업데이트 처리를 시작하거나 갱신할 수 있을 때까지 대기합니다. 이는 의도된 동작이지만, 필요한 종속성을 제때 해결하지 않으면 데이터 가용성이 지연될 수 있습니다. 따라서 갱신 가능 materialized view를 생성하기 전에 모든 종속성이 올바르게 정의되어 있고 실제로 존재하는지 확인해야 합니다.
- 현재로서는 mv와 해당 종속성 사이에 실제 “dbt 연동”이 없으므로 생성 순서는 보장되지 않습니다.
- 갱신 가능 기능은 여러 mv가 동일한 대상 모델을 가리키는 경우에 대해서는 테스트되지 않았습니다.