Back to Blog
dbtMaterializationIncrementalModelsData Modeling

0x02. dbt Models & Materialization - 모델이 웨어하우스에 저장되는 네 가지 방식

View, Table, Incremental, Ephemeral — 데이터 크기와 용도에 따라 Materialization 전략을 선택하는 기준을 정리한다.

Materialization이란

Materialization은 dbt 모델이 웨어하우스에 어떤 형태로 저장되는지를 결정하는 전략이다. 같은 SELECT 문이라도 View로 만들 수도 있고, 물리적 Table로 만들 수도 있다. 이 선택이 빌드 시간, 쿼리 성능, 스토리지 비용을 좌우한다.

{{ config(materialized='incremental') }}

SELECT * FROM {{ ref('stg_orders') }}

모델 파일 상단에 config() 블록으로 지정하거나, dbt_project.yml에서 폴더 단위로 일괄 설정할 수 있다.

네 가지 전략

전략저장빌드 시간쿼리 성능사용처
ViewX즉시느림Staging
TableO매번 전체빠름Marts (소규모)
IncrementalO변경분만빠름Marts (대규모 팩트)
EphemeralX없음-중간 CTE

View

{{ config(materialized='view') }}

SELECT
    id AS order_id,
    user_id AS customer_id,
    order_date,
    status
FROM {{ source('jaffle_shop', 'orders') }}

매 쿼리 시 SQL을 재실행한다. 저장 공간이 필요 없고 항상 최신 데이터를 보장한다. Staging 레이어에 적합하다.

장점: 스토리지 비용 0, 항상 최신 단점: 매번 소스를 스캔하므로 쿼리가 느릴 수 있음 사용처: 소스 데이터의 컬럼명 표준화, 타입 캐스팅 같은 가벼운 변환

Table

{{ config(materialized='table') }}

SELECT
    customer_id,
    COUNT(*) AS total_orders,
    SUM(amount) AS lifetime_value
FROM {{ ref('stg_orders') }}
GROUP BY customer_id

결과를 물리적 테이블로 저장한다. dbt runDROP + CREATE로 완전 재빌드한다. 자주 쿼리되는 집계 테이블에 적합하다.

장점: 쿼리 빠름, 인덱스 적용 가능 단점: 매 빌드마다 전체 재생성, 대용량이면 느림 사용처: Marts 레이어의 소규모 디멘션/팩트 테이블

Incremental

대용량 테이블의 핵심 전략이다. 신규/변경된 행만 INSERT 또는 MERGE한다.

{{ config(
    materialized='incremental',
    unique_key='order_id',
    incremental_strategy='merge'
) }}

SELECT
    order_id,
    customer_id,
    order_date,
    status,
    amount,
    updated_at
FROM {{ source('jaffle_shop', 'orders') }}

{% if is_incremental() %}
  WHERE updated_at > (SELECT MAX(updated_at) FROM {{ this }})
{% endif %}

동작 방식

  1. 첫 실행: is_incremental()이 false → WHERE 절 무시 → 전체 데이터 로드
  2. 이후 실행: is_incremental()이 true → {{ this }}(기존 테이블)의 MAX 타임스탬프 이후 데이터만 로드
  3. unique_key를 지정하면 MERGE 전략으로 기존 행 UPDATE + 신규 행 INSERT

Incremental 전략 비교

전략동작지원 DW적합한 경우
appendINSERT만모두이벤트 로그 (업데이트 없음)
mergeMERGE (upsert)BigQuery, Snowflake, Databricks업데이트 있는 데이터
delete+insert기존 삭제 후 INSERTRedshift, Postgresmerge 미지원 DW
insert_overwrite파티션 단위 교체BigQuery, Spark파티션 기반 대용량

주의사항

  • Late-arriving data: 타임스탬프 기반 필터링은 늦게 도착한 데이터를 놓칠 수 있다. lookback window를 추가하거나 --full-refresh 주기적 실행을 권장
  • 스키마 변경: 컬럼 추가 시 on_schema_change='append_new_columns' 설정 필요
  • 전체 재빌드: dbt run --full-refresh -s model_name으로 언제든 전체 재빌드 가능

수억 행 팩트 테이블에서 빌드 시간을 몇 시간에서 몇 분으로 줄일 수 있다.

Ephemeral

{{ config(materialized='ephemeral') }}

SELECT
    order_id,
    CASE
        WHEN amount >= 1000 THEN 'high'
        WHEN amount >= 100  THEN 'medium'
        ELSE 'low'
    END AS order_tier
FROM {{ ref('stg_orders') }}

웨어하우스에 물리적 객체를 생성하지 않는다. 이 모델을 참조하는 다른 모델에 CTE(Common Table Expression)로 인라인된다.

장점: 오브젝트 생성 안 함, 깔끔한 모듈화 단점: 디버깅 어려움 (직접 쿼리 불가), dbt Docs에서 리니지 안 보임 사용처: 가벼운 중간 계산, 재사용 빈도 낮은 변환

선택 가이드

소스 정리  View
├── 중간 변환 (재사용 낮음)  Ephemeral
├── 중간 변환 (재사용 높음)  View
└── 최종 테이블
    ├── 소규모 (< 수백만 )  Table
    └── 대규모 (수억+ )  Incremental

dbt_project.yml에서 폴더 단위로 기본값을 설정하면 편하다:

models:
  my_project:
    staging:
      +materialized: view
    intermediate:
      +materialized: ephemeral
    marts:
      +materialized: table

개별 모델에서 config()로 오버라이드할 수 있다. 예를 들어 marts 폴더의 기본은 table이지만, 대용량 팩트 테이블만 incremental로 지정한다.