Skip to main content
이 섹션에서는 dbt와 ClickHouse 어댑터를 설정하는 가이드와, 공개적으로 제공되는 IMDB 데이터셋을 사용해 ClickHouse에서 dbt를 사용하는 예시를 제공합니다. 이 예시에서는 다음 단계를 다룹니다:
  1. dbt 프로젝트를 생성하고 ClickHouse 어댑터를 설정합니다.
  2. 모델을 정의합니다.
  3. 모델을 업데이트합니다.
  4. 증분 모델을 생성합니다.
  5. 스냅샷 모델을 생성합니다.
  6. materialized view를 사용합니다.
이 가이드는 문서, 기능 및 구성, materializations 참고와 함께 사용할 수 있도록 작성되었습니다.

설정

환경을 준비하려면 dbt 및 ClickHouse 어댑터 설정 섹션의 안내를 따르십시오. 중요: 아래 내용은 Python 3.9 환경에서 테스트되었습니다.

ClickHouse 준비

dbt는 고도로 관계형인 데이터를 모델링하는 데 매우 적합합니다. 예시를 위해 아래와 같은 관계형 스키마를 가진 소규모 IMDB 데이터셋을 제공합니다. 이 데이터셋은 관계형 데이터셋 리포지토리에서 가져왔습니다. 이는 dbt와 함께 일반적으로 사용하는 스키마와 비교하면 매우 단순하지만, 다루기 쉬운 예시로는 적절합니다. 표시된 것처럼 이들 테이블의 부분 집합을 사용합니다. 다음 테이블을 생성하십시오:
테이블 rolescreated_at 컬럼 기본값은 now()입니다. 이는 나중에 모델의 증분 업데이트를 식별하는 데 사용합니다. 자세한 내용은 증분 모델을 참조하십시오.
데이터를 삽입하기 위해 s3 함수를 사용해 공개 엔드포인트에서 소스 데이터를 읽습니다. 테이블을 채우려면 다음 명령을 실행하세요:
실행 시간은 사용 중인 대역폭에 따라 달라질 수 있지만, 각 작업은 완료까지 몇 초밖에 걸리지 않습니다. 다음 쿼리를 실행하여 영화 출연 횟수 순으로 각 배우의 요약을 계산하고, 데이터가 성공적으로 로드되었는지 확인하십시오:
응답은 다음과 같아야 합니다:
이후 가이드에서는 이 쿼리를 모델로 변환한 뒤, ClickHouse에서 dbt 뷰와 테이블로 구체화합니다.

ClickHouse에 연결하기

  1. dbt 프로젝트를 생성합니다. 여기서는 imdb 소스의 이름을 따서 프로젝트 이름을 지정합니다. 프롬프트가 표시되면 데이터베이스 소스로 clickhouse를 선택합니다.
  2. 프로젝트 폴더로 이동합니다.
  3. 이제 원하는 텍스트 편집기가 필요합니다. 아래 예시에서는 널리 사용되는 VS Code를 사용합니다. IMDB 디렉터리를 열면 yml 파일과 sql 파일이 있는 것을 확인할 수 있습니다.
  4. dbt_project.yml 파일을 업데이트하여 첫 번째 model인 actor_summary를 지정하고, profile을 clickhouse_imdb로 설정합니다.
  5. 다음으로 dbt에 ClickHouse 인스턴스의 연결 정보를 제공해야 합니다. 아래 내용을 ~/.dbt/profiles.yml에 추가합니다.
    userpassword는 환경에 맞게 수정해야 합니다. 추가로 사용할 수 있는 설정은 여기에 문서화되어 있습니다.
  6. IMDB 디렉터리에서 dbt debug 명령을 실행하여 dbt가 ClickHouse에 연결할 수 있는지 확인합니다.
    응답에 Connection test: [OK connection ok]가 포함되어 있는지 확인하십시오. 이는 연결이 성공했음을 의미합니다.

간단한 뷰 머티리얼라이즈 만들기

뷰 머티리얼라이즈를 사용하면 모델은 실행할 때마다 ClickHouse의 CREATE VIEW AS 구문을 통해 뷰로 다시 생성됩니다. 이 방식은 데이터를 추가로 저장할 필요는 없지만, 테이블 머티리얼라이즈보다 쿼리 성능이 느립니다.
  1. imdb 폴더에서 models/example 디렉터리를 삭제하십시오:
  2. models 폴더의 actors 내에 새 파일을 만드세요. 여기서는 각 파일이 하나의 actor 모델을 나타내도록 생성합니다:
  3. models/actors 폴더에 schema.ymlactor_summary.sql 파일을 생성하세요.
    파일 schema.yml에서 테이블을 정의합니다. 이렇게 정의한 테이블은 이후 매크로에서 사용할 수 있습니다. Edit models/actors/schema.yml 파일을 편집하여 다음 내용을 포함하도록 하세요:
    actors_summary.sql은 실제 model을 정의합니다. config 함수에서는 이 model이 ClickHouse에서 뷰로 구체화되도록 요청한다는 점에 유의하십시오. 테이블은 schema.yml 파일에서 source 함수를 통해 참조합니다. 예를 들어 source('imdb', 'movies')imdb 데이터베이스의 movies 테이블을 가리킵니다. models/actors/actors_summary.sql을 편집하여 다음 내용을 포함하도록 하십시오:
    최종 actor_summary에 updated_at 컬럼을 포함했다는 점에 유의하십시오. 이는 이후 증분 머티리얼라이제이션에 사용됩니다.
  4. imdb 디렉터리에서 dbt run 명령을 실행하세요.
  5. dbt는 요청한 대로 model을 ClickHouse의 뷰로 생성합니다. 이제 이 뷰에 직접 쿼리할 수 있습니다. 이 뷰는 imdb_dbt 데이터베이스에 생성되며, 이는 clickhouse_imdb profile 아래 ~/.dbt/profiles.yml 파일의 스키마(schema) 매개변수로 결정됩니다.
    이 뷰를 쿼리하면 더 간단한 구문으로 앞서 실행한 쿼리의 결과를 재현할 수 있습니다:

테이블 머티리얼라이즈 생성하기

이전 예시에서 모델은 뷰로 구체화되었습니다. 일부 쿼리에는 이것만으로도 충분한 성능을 제공할 수 있지만, 더 복잡한 SELECT 또는 자주 실행되는 쿼리는 테이블로 구체화하는 편이 더 적합할 수 있습니다. 이러한 머티리얼라이즈는 BI 도구에서 조회할 모델에 유용하며, 더 빠른 사용 경험을 제공하는 데 도움이 됩니다. 이는 사실상 쿼리 결과를 새로운 테이블로 저장하므로, 그에 따른 스토리지 오버헤드가 발생합니다. 즉, INSERT TO SELECT가 실행됩니다. 이 테이블은 매번 다시 생성되며, 다시 말해 증분 방식이 아닙니다. 따라서 큰 결과 집합은 실행 시간이 길어질 수 있습니다. 자세한 내용은 dbt 제한 사항을 참조하십시오.
  1. actors_summary.sql 파일을 수정하여 materialized 매개변수를 table로 설정합니다. ORDER BY가 어떻게 정의되어 있는지와 MergeTree 테이블 엔진을 사용한다는 점에 주목하십시오:
  2. imdb 디렉터리에서 dbt run 명령을 실행합니다. 이 작업은 완료까지 약간 더 오래 걸릴 수 있으며, 대부분의 환경에서 약 10초 정도 소요됩니다.
  3. imdb_dbt.actor_summary 테이블이 생성되었는지 확인합니다:
    적절한 데이터 타입으로 정의된 테이블이 표시되어야 합니다:
  4. 이 테이블의 결과가 이전 응답과 일치하는지 확인합니다. 이제 모델이 테이블로 구체화되었으므로 응답 시간이 눈에 띄게 개선된 것을 확인할 수 있습니다:
    이 모델에 대해 다른 쿼리도 자유롭게 실행해 보십시오. 예를 들어, 5편을 초과해 출연한 배우 중 평균 평점이 가장 높은 배우는 누구인지 확인할 수 있습니다.

증분 머티리얼라이즈 생성하기

이전 예시에서는 모델을 머티리얼라이즈하기 위한 테이블을 생성했습니다. 이 테이블은 dbt를 실행할 때마다 다시 생성됩니다. 결과 집합이 크거나 변환이 복잡한 경우, 이 방식은 현실적이지 않거나 비용이 매우 많이 들 수 있습니다. 이러한 문제를 해결하고 빌드 시간을 줄이기 위해 dbt는 증분 머티리얼라이즈를 제공합니다. 이를 사용하면 마지막 실행 이후 변경된 레코드를 테이블에 삽입하거나 업데이트할 수 있으므로 이벤트성 데이터에 적합합니다. 내부적으로는 먼저 업데이트된 모든 레코드가 포함된 임시 테이블을 만든 다음, 변경되지 않은 레코드와 업데이트된 레코드를 모두 새 대상 테이블(target table)에 삽입합니다. 따라서 큰 결과 집합에서는 table 모델과 유사한 제약 사항이 있습니다. 큰 집합에서 이러한 제약을 극복하기 위해 어댑터는 ‘inserts_only’ 모드를 지원합니다. 이 모드에서는 임시 테이블을 만들지 않고 모든 업데이트를 대상 테이블에 삽입합니다(자세한 내용은 아래 참조). 이 예시를 설명하기 위해 배우 “Clicky McClickHouse”를 추가하겠습니다. 그는 무려 910편의 영화에 출연하며, Mel Blanc보다도 더 많은 영화에 출연한 인물이 됩니다.
  1. 먼저 model을 incremental 유형으로 변경합니다. 이를 추가하려면 다음이 필요합니다:
    1. unique_key - 어댑터가 행을 고유하게 식별할 수 있도록 unique_key를 제공해야 합니다. 이 경우 쿼리의 id 필드면 충분합니다. 이렇게 하면 구체화된 테이블(Materialized Table)에 중복 행이 생기지 않습니다. 고유성 제약 조건에 관한 자세한 내용은 여기를 참조하십시오.
    2. Incremental filter - 또한 증분 실행 시 변경된 행을 dbt가 어떻게 식별할지 지정해야 합니다. 이는 델타 표현식을 제공해 구현합니다. 일반적으로 이벤트 데이터에는 타임스탬프를 사용하므로, 여기서는 updated_at 타임스탬프 필드를 사용합니다. 이 컬럼은 행이 삽입될 때 기본값으로 now()를 사용하므로 새로운 역할을 식별할 수 있습니다. 또한 새 액터가 추가되는 경우도 식별해야 합니다. 기존 구체화된 테이블을 나타내는 {{this}} 변수를 사용하면 where id > (select max(id) from {{ this }}) or updated_at > (select max(updated_at) from {{this}})라는 표현식을 만들 수 있습니다. 이 표현식은 {% if is_incremental() %} 조건 안에 넣어 테이블을 처음 생성할 때가 아니라 증분 실행에서만 사용되도록 합니다. 증분 모델에서 행을 필터링하는 방법에 관한 자세한 내용은 dbt 문서의 이 설명을 참조하십시오.
    actor_summary.sql 파일을 다음과 같이 수정하십시오:
    이 모델은 rolesactors 테이블의 업데이트와 추가에만 반응합니다. 모든 테이블에 반응하도록 하려면 이 모델을 여러 개의 하위 모델로 나누고, 각 하위 모델에 자체 증분 기준을 설정하는 것이 좋습니다. 이후 이러한 모델은 서로 참조하고 연결할 수 있습니다. 모델 간 교차 참조에 대한 자세한 내용은 here를 참조하십시오.
  2. dbt run을 실행하고 생성된 테이블의 결과를 확인합니다:
  3. 이제 증분 업데이트를 보여주기 위해 모델에 데이터를 추가하겠습니다. actors 테이블에 배우 “Clicky McClickHouse”를 추가하세요:
  4. “Clicky”가 무작위로 선택된 영화 910편에 출연하게 해보겠습니다:
  5. 기반이 되는 원본 테이블을 직접 쿼리해 dbt 모델을 거치지 않고, 이제 그가 실제로 가장 많이 출연한 배우인지 확인합니다:
  6. dbt run을 실행한 뒤 모델이 업데이트되어 위의 결과와 일치하는지 확인하세요:

내부 동작

위와 같은 증분 업데이트를 위해 실행된 SQL 문은 ClickHouse의 쿼리 로그를 조회하면 확인할 수 있습니다.
위 쿼리를 실행 기간에 맞게 조정하십시오. 결과 확인은 사용자가 직접 해야 하지만, 어댑터가 증분 업데이트를 수행할 때 사용하는 일반적인 전략은 다음과 같습니다.
  1. 어댑터는 임시 테이블 actor_sumary__dbt_tmp를 생성합니다. 변경된 행은 이 테이블로 스트리밍됩니다.
  2. 새 테이블 actor_summary_new,를 생성합니다. 그런 다음 이전 테이블의 행을 이전 테이블에서 새 테이블로 스트리밍하면서, 행 ID가 임시 테이블에 존재하지 않는지 확인합니다. 이렇게 하면 업데이트와 중복을 효과적으로 처리할 수 있습니다.
  3. 임시 테이블의 결과를 새 actor_summary 테이블로 스트리밍합니다:
  4. 마지막으로 EXCHANGE TABLES 구문을 통해 새 테이블을 이전 버전과 원자적으로 교환합니다. 이후 이전 테이블과 임시 테이블을 삭제합니다.
이는 아래와 같이 시각화됩니다: 이 전략은 매우 큰 모델에서는 문제가 될 수 있습니다. 자세한 내용은 제한 사항을 참조하십시오.

Append Strategy (삽입 전용 모드)

증분 모델에서 대규모 데이터셋의 한계를 보완하기 위해 어댑터는 dbt 구성 매개변수 incremental_strategy를 사용합니다. 이 매개변수는 append 값으로 설정할 수 있습니다. 이렇게 설정하면 업데이트된 행이 대상 테이블(즉, imdb_dbt.actor_summary)에 직접 삽입되고, 임시 테이블은 생성되지 않습니다. 참고: Append only 모드를 사용하려면 데이터가 불변이어야 하거나 중복이 허용되어야 합니다. 변경된 행을 지원하는 증분 테이블 모델이 필요하다면 이 모드를 사용하지 마십시오. 이 모드를 설명하기 위해 새 배우를 한 명 더 추가한 다음, incremental_strategy='append'로 dbt run을 다시 실행하겠습니다.
  1. actor_summary.sql에서 append only 모드를 구성합니다:
  2. 유명한 배우를 한 명 더 추가합니다. Danny DeBito입니다.
  3. Danny를 무작위 영화 920편에 출연시킵니다.
  4. dbt run을 실행하고 actor-summary 테이블에 Danny가 추가되었는지 확인합니다.
이 증분 실행이 “Clicky”를 삽입했을 때보다 얼마나 더 빨랐는지 확인해 보십시오. query_log 테이블을 다시 확인하면 두 번의 증분 실행 간 차이를 확인할 수 있습니다:
이번 실행에서는 새 행만 imdb_dbt.actor_summary 테이블에 바로 추가되고, 테이블은 생성되지 않습니다.

삭제 및 삽입 모드(실험적)

ClickHouse는 그동안 비동기 뮤테이션 형태로만 업데이트와 삭제를 제한적으로 지원해 왔습니다. 이는 IO 사용량이 매우 클 수 있으므로 일반적으로 피하는 것이 좋습니다. ClickHouse 22.8에서는 경량한 삭제가 도입되었고, ClickHouse 25.7에서는 경량 업데이트가 도입되었습니다. 이러한 기능이 도입되면서 비동기적으로 구체화되더라도 단일 업데이트 쿼리로 인한 변경 사항은 사용자 관점에서 즉시 반영됩니다. 이 모드는 모델에서 incremental_strategy 매개변수로 구성할 수 있습니다. 즉,
이 전략은 대상 모델의 테이블에서 직접 수행되므로 작업 중 문제가 발생하면 증분 모델의 데이터가 유효하지 않은 상태가 될 가능성이 높습니다. atomic update가 없기 때문입니다. 요약하면, 이 접근 방식은 다음과 같습니다:
  1. 어댑터가 임시 테이블 actor_sumary__dbt_tmp를 생성합니다. 변경된 행은 이 테이블로 스트리밍됩니다.
  2. 현재 actor_summary 테이블에 DELETE를 실행합니다. actor_sumary__dbt_tmp의 id를 기준으로 행을 삭제합니다.
  3. actor_sumary__dbt_tmp의 행을 INSERT INTO actor_summary SELECT * FROM actor_sumary__dbt_tmp를 사용해 actor_summary에 삽입합니다.
이 과정은 아래에 나와 있습니다:

insert_overwrite 모드 (실험적)

다음 단계를 수행합니다:
  1. 증분 모델 릴레이션과 동일한 구조의 스테이징(임시) 테이블을 생성합니다: CREATE TABLE {staging} AS {target}.
  2. 새 레코드만(SELECT로 생성된 레코드) 스테이징 테이블에 삽입합니다.
  3. 새 파티션만(스테이징 테이블에 있는 파티션) 대상 테이블로 교체합니다.

이 접근 방식에는 다음과 같은 장점이 있습니다:
  • 전체 테이블을 복사하지 않으므로 기본 전략보다 더 빠릅니다.
  • INSERT 작업이 성공적으로 완료될 때까지 원본 테이블을 수정하지 않으므로 다른 전략보다 더 안전합니다. 중간에 실패하더라도 원본 테이블은 수정되지 않습니다.
  • 데이터 엔지니어링 모범 사례인 “파티션 불변성”을 구현합니다. 따라서 증분 및 병렬 데이터 처리, 롤백 등이 더 단순해집니다.

스냅샷 생성

dbt 스냅샷은 시간에 따라 변경되는 모델의 변경 이력을 기록할 수 있게 해줍니다. 이를 통해 모델에 대해 특정 시점 기준 쿼리를 수행할 수 있으며, 분석가는 모델의 이전 상태를 “과거 시점으로 되돌아가” 확인할 수 있습니다. 이는 행이 유효한 기간을 기록하는 시작일 및 종료일 컬럼을 사용하는 type-2 Slowly Changing Dimensions 방식으로 구현됩니다. 이 기능은 ClickHouse 어댑터에서 지원되며, 아래에서 예시를 보여줍니다. 이 예시는 Creating an Incremental Table Model을 완료한 상태라고 가정합니다. actor_summary.sql에서 inserts_only=True를 설정하지 마십시오. models/actor_summary.sql은 다음과 같아야 합니다:
  1. snapshots 디렉터리에 actor_summary 파일을 생성합니다.
  2. actor_summary.sql 파일의 내용을 다음과 같이 업데이트합니다.
이 내용과 관련해 몇 가지 참고할 사항이 있습니다.
  • select 쿼리는 시간에 따라 스냅샷으로 저장할 결과를 정의합니다. ref 함수는 앞서 생성한 actor_summary 모델을 참조하는 데 사용됩니다.
  • 레코드 변경을 나타내려면 timestamp 컬럼이 필요합니다. 여기서는 updated_at 컬럼(증분 테이블 모델 생성 참고)을 사용할 수 있습니다. strategy 매개변수는 업데이트를 표시하는 데 timestamp를 사용함을 의미하며, updated_at 매개변수는 사용할 컬럼을 지정합니다. 모델에 이 컬럼이 없다면 대신 check strategy를 사용할 수도 있습니다. 이 방법은 훨씬 비효율적이며, 비교할 컬럼 목록을 사용자가 직접 지정해야 합니다. dbt는 이 컬럼들의 현재 값과 과거 값을 비교해 변경 사항을 기록합니다(값이 같으면 아무 작업도 수행하지 않음).
  1. dbt snapshot 명령을 실행합니다.
actor_summary_snapshot 테이블이 snapshots DB에 생성된 것을 확인할 수 있습니다. 이는 target_schema 매개변수로 결정됩니다.
  1. 이 데이터를 샘플링해 보면 dbt가 dbt_valid_from 및 dbt_valid_to 컬럼을 포함했음을 확인할 수 있습니다. 후자의 값은 NULL로 설정되어 있습니다. 이후 실행에서 이 값이 업데이트됩니다.
  2. 가장 좋아하는 배우 Clicky McClickHouse가 영화 10편에 더 출연하도록 하세요.
  3. imdb 디렉터리에서 dbt run 명령을 다시 실행하세요. 그러면 증분 모델이 업데이트됩니다. 이 작업이 완료되면 변경 사항을 기록하기 위해 dbt snapshot을 실행하세요.
  4. 이제 스냅샷을 쿼리하면 Clicky McClickHouse에 대한 행이 2개 있는 것을 확인할 수 있습니다. 이전 항목에는 이제 dbt_valid_to 값이 설정되어 있습니다. 새 값은 dbt_valid_from 컬럼에 동일한 값으로 기록되고, dbt_valid_to 값은 null입니다. 새 행이 있었다면 이 역시 스냅샷에 추가되었을 것입니다.
dbt 스냅샷에 대한 자세한 내용은 여기를 참조하십시오.

seed 사용하기

dbt는 CSV 파일에서 데이터를 로드하는 기능을 제공합니다. 이 기능은 데이터베이스의 대규모 내보내기 데이터를 로드하는 데는 적합하지 않으며, 일반적으로 코드 테이블과 dictionaries에 사용하는 작은 파일에 더 적합합니다. 예를 들어 국가 코드를 국가 이름에 매핑할 때 사용할 수 있습니다. 간단한 예시로, seed 기능을 사용해 장르 코드 목록을 생성한 다음 업로드해 보겠습니다.
  1. 기존 데이터셋에서 장르 코드 목록을 생성합니다. dbt 디렉터리에서 clickhouse-client를 사용해 seeds/genre_codes.csv 파일을 만드십시오.
  2. dbt seed 명령을 실행합니다. 그러면 데이터베이스 imdb_dbt에 새 테이블 genre_codes가 생성되고(이는 schema 구성에 정의되어 있음), CSV 파일의 행이 이 테이블에 로드됩니다.
  3. 데이터가 로드되었는지 확인합니다:

추가 정보

이전 가이드에서는 dbt 기능의 일부만 간략히 소개했습니다. 자세한 내용은 우수한 dbt 문서를 참조하십시오.
마지막 수정일 2026년 7월 24일