freshness

freshness

freshness config로 source 또는 model 데이터가 얼마나 신선해야 하는지 선언할 수 있어요. warn_aftererror_after 임계값을 걸어두고 dbt freshness를 실행하면 데이터가 오래됐을 때 경고나 오류를 받을 수 있어요.

출처: 문서

본문

freshness가 구성된 모든 source와 model을 확인하려면 dbt freshness를 실행하세요.

(dbt v2.0 이상 적용)

note

dbt source freshness는 source 확인하는 레거시 명령이에요. 하위 호환성을 위해 여전히 지원되고 sources.json도 계속 생성하지만, 앞으로는 dbt freshness 사용을 권장해요.

구성 (Configuration)

달리 명시하지 않는 한 다음 필드로 source와 model의 freshness를 구성해요:

필드 설명
warn_after 최신 데이터가 얼마나 오래되면 freshness 검사가 경고를 보고하는지 정의해요. countperiod가 모두 필요해요.
error_after 최신 데이터가 얼마나 오래되면 freshness 검사가 오류를 보고하는지 정의해요. warn_after와 같은 형식이에요.
loaded_at_field dbt가 최근 로드된 타임스탬프를 파악하기 위해 조회하는 컬럼이에요. 어댑터 메타데이터를 사용할 수 없을 때 필요해요.
loaded_at_query 가장 최근 로드된 타임스탬프를 반환하는 SQL 표현식이에요. loaded_at_field의 대안이에요. 같은 리소스에 loaded_at_queryloaded_at_field를 둘 다 설정하면 파싱 오류가 발생해요. dbt v1.10 이상에서 사용 가능해요.
filter freshness 쿼리에 WHERE 절을 추가해 스캔할 데이터를 제한해요. BigQuery 파티션 테이블이나 Snowflake, Databricks, Spark의 대형 테이블에 유용해요. source나 model의 다른 용도에는 영향을 주지 않아요. loaded_at_query에는 적용되지 않아요.

warn_aftererror_after 중 하나 또는 둘 다 제공할 수 있어요. 둘 다 설정하지 않으면 dbt는 해당 리소스의 freshness를 확인하지 않아요. warn_aftererror_after 각각은 countperiod가 모두 필요하며, 하나만 설정하면 파싱 경고가 발생하지만 dbt freshness 실행 시 오류가 돼요.

Source freshness

Project 파일

dbt_project.yml

sources:
  <resource-path>:
    +freshness:
      warn_after:
        count: <positive_integer>
        period: minute | hour | day

Property 파일

models/.yml

sources:
  - name: <source_name>
    config:
      freshness: # changed to config in v1.9
        warn_after:
          count: <positive_integer>
          period: minute | hour | day
        error_after:
          count: <positive_integer>
          period: minute | hour | day
        filter: <boolean_sql_expression>
      # changed to config in v1.10
      loaded_at_field: <column_name_or_expression>
      # or use loaded_at_query in v1.10 or higher
      loaded_at_query: <sql_expression>

    tables:
      - name: <table_name>
        config:
          # source.table.config.freshness overrides source.config.freshness
          freshness:
            warn_after:
              count: <positive_integer>
              period: minute | hour | day
            error_after:
              count: <positive_integer>
              period: minute | hour | day
            filter: <boolean_sql_expression>
          loaded_at_field: <column_name_or_expression>
          loaded_at_query: <sql_expression>

Freshness 블록은 계층적으로 적용돼요:

  • source에 설정한 freshnessloaded_at_field는 해당 source의 모든 테이블에 적용돼요.
  • source 테이블에 설정한 freshnessloaded_at_field는 source 레벨 값을 덮어써요.

source를 freshness 계산에서 제외하려면 명시적으로 freshness: null로 설정하세요.

source에 freshness: 블록이 있으면 dbt가 해당 source의 freshness를 계산하려 해요:

  • loaded_at_field가 제공되면 dbt는 select 쿼리로 freshness를 계산해요.
  • loaded_at_field가 제공되지 않으면 dbt는 가능할 때 웨어하우스 메타데이터 테이블로 freshness를 계산해요.

(dbt v1.12 이상 적용)

와일드카드 테이블 식별자

BigQuery에서 와일드카드 테이블 식별자(events_* 등)로 정의된 source에는 메타데이터 기반 freshness 검사가 신뢰할 수 없어요.

잘못된 freshness 결과를 방지하려면 dbt_project.yml에서 bigquery_reject_wildcard_metadata_source_freshness 플래그를 활성화하세요. 활성화하면 메타데이터 기반 freshness가 와일드카드 테이블 식별자와 함께 사용될 때 dbt가 오류를 발생시켜요.

와일드카드 테이블의 freshness를 계산하려면 loaded_at_field를 구성해 쿼리 기반 freshness 검사를 사용하세요.

예제

loaded_at_field 사용하기
sources:
  - name: jaffle_shop
    # Cast a date field to timestamp
    loaded_at_field: "completed_date::timestamp"

    tables:
      - name: orders
        # Cast a non-UTC timestamp to UTC
        loaded_at_field: "convert_timezone('Australia/Sydney', 'UTC', created_at_local)"
        config:
          freshness:
            warn_after: {count: 12, period: hour}
loaded_at_query 사용하기

(dbt v1.10 이상 적용)

sources:
  - name: jaffle_shop
    tables:
      - name: orders
        loaded_at_query: |
          select max(_sdc_batched_at) from (
            select * from {{ this }}
            where _sdc_batched_at > dateadd(day, -7, current_date)
            qualify count(*) over (partition by _sdc_batched_at::date) > 2000
          )
        config:
          freshness:
            warn_after: {count: 12, period: hour}
완전한 예시

models/.yml

sources:
  - name: jaffle_shop
    database: raw
    config:
      freshness: # default freshness for all tables
        warn_after: {count: 12, period: hour}
        error_after: {count: 24, period: hour}
      loaded_at_field: _etl_loaded_at

    tables:
      - name: customers # uses the freshness defined above

      - name: orders
        config:
          freshness: # more strict for orders
            warn_after: {count: 6, period: hour}
            error_after: {count: 12, period: hour}
            filter: datediff('day', _etl_loaded_at, current_timestamp) < 2

      - name: product_skus
        config:
          freshness: null # do not check freshness for this table

(dbt v2.0 이상 적용)

dbt freshness를 실행하면 orders 테이블에 대해 다음 쿼리가 실행돼요:

컴파일된 SQL
select
  max(_etl_loaded_at) as max_loaded_at,
  convert_timezone('UTC', current_timestamp()) as snapshotted_at
from raw.jaffle_shop.orders
where datediff('day', _etl_loaded_at, current_timestamp) < 2
Jinja SQL
select
  max({{ loaded_at_field }}) as max_loaded_at,
  {{ current_timestamp() }} as snapshotted_at
from {{ source }}
{% if filter %}
where {{ filter }}
{% endif %}

소스 코드

(dbt v2.0 이상 적용)

Model freshness

모델에 freshness config를 사용하려면:

  • Freshness 임계값 설정: warn_aftererror_after 임계값을 설정해 모델 데이터가 얼마나 오래될 수 있는지 선언하고, dbt freshness를 실행해 각 모델을 임계값과 비교해 경고나 오류를 보고하게 하세요.
  • 빌드 예약(build_after): 새 업스트림 데이터가 있을 때 모델이 얼마나 자주 재빌드될지 제어해요. dbt platform Enterprise 티어에서만 사용할 수 있어요. build_after는 상태 인지 오케스트레이션(state-aware orchestration)의 일부였으며, 이는 비권장되고 현재는 dbt State예요.

Project 파일

dbt_project.yml

models:
  <resource-path>:
    +loaded_at_field: <column_name>    # or loaded_at_query
    +loaded_at_query: <sql_expression> # alternative to loaded_at_field
    +freshness:
      warn_after: {count: <positive_integer>, period: minute | hour | day}
      error_after: {count: <positive_integer>, period: minute | hour | day}

Property 파일

models/.yml

models:
  - name: stg_orders
    config:
      loaded_at_field: updated_at    # or loaded_at_query
      freshness:
        warn_after: {count: 24, period: hour}
        error_after: {count: 48, period: hour}

SQL 파일 config

models/.sql

{{
    config(
      loaded_at_field="updated_at",
      freshness={
        "warn_after": {"count": 24, "period": "hour"},
        "error_after": {"count": 48, "period": "hour"}
      }
    )
}}

모든 materialization이 freshness 검사를 같은 방식으로 지원하는 건 아니에요. dbt는 파싱 시점에 config를 검증하고 잘못된 조합에 대해 오류를 발생시켜요.

Materialization loaded_at_field / loaded_at_query 동작
table, incremental, materialized_view, dynamic_table 선택 설정하지 않으면 dbt가 어댑터 메타데이터(예: 테이블의 마지막 수정 시각)로 대체해요.
view, external 필수 View는 행 레벨 메타데이터를 노출하지 않아요. freshness를 측정하려면 loaded_at_field 또는 loaded_at_query를 설정하세요. 빈 문자열(loaded_at_field: \"\")은 설정하지 않은 것과 동일하게 처리되며 파싱 오류를 발생시켜요.
ephemeral 지원 안 함 측정할 materialize된 것이 없어요. 파싱 오류를 발생시켜요.

불완전한 freshness 규칙(예: count만 있고 period가 없는 warn_after)은 모든 materialization에 대해 파싱 시 경고를 발생시켜요. dbt rundbt build는 경고만 하고 여전히 성공해요. 오직 dbt freshness만 이를 오류로 취급해요. 별도로, viewexternal 모델은 loaded_at_field 또는 loaded_at_query가 필요해요 — 둘 다 빼먹으면 freshness 규칙이 완전한지와 무관하게 dbt run, dbt build, dbt freshness가 실패하는 파싱 오류이에요.

크로스 프로젝트 freshness (Cross-project freshness)

dbt Mesh의 public model에 대해 dbt는 freshness config를 저장해서, 다운스트림 프로젝트가 업스트림 프로젝트를 실행하지 않고도 업스트림 모델의 freshness를 확인할 수 있게 해줘요.

예를 들어 project_a가 freshness가 구성된 public model orders를 소유한다고 해볼게요:

# project_a: models/orders.yml
models:
  - name: orders
    access: public
    config:
      loaded_at_field: updated_at
      freshness:
        warn_after: {count: 24, period: hour}
        error_after: {count: 48, period: hour}

project_borders에 의존해요. orders 데이터가 신선한지 확인하려면 project_b에서 dbt freshness를 실행하세요 — project_a를 다시 실행할 필요가 없어요:

# run from project_b
dbt freshness --select project_a.orders

예제

warn_after만 사용하기
models:
  - name: stg_orders
    config:
      materialized: table
      freshness:
        warn_after: {count: 24, period: hour}
loaded_at_query 사용하기
models:
  - name: stg_events
    config:
      materialized: table
      freshness:
        warn_after: {count: 6, period: hour}
        error_after: {count: 12, period: hour}
      loaded_at_query: "select max(_loaded_at) from {{ this }} where _batch_complete = true"

빌드 예약 (Scheduling builds)

상태 인지 오케스트레이션은 이제 dbt State

dbt State는 모든 엔진과 환경(dbt v1, dbt platform, dbt v2)에서 동작해요.

2026년 6월 1일 이전에 상태 인지 오케스트레이션을 사용하고 있었다면 계속 사용할 수 있어요. 무료 dbt State 체험판을 시작하면 표준 30일 기간을 넘어 연장돼요. 연장이 계정에 적용되지 않으면 계정 팀에 문의하세요. 시작하려면 상태 인지 오케스트레이션에서 마이그레이션을 참고하세요.

Project 파일

dbt_project.yml

models:
  <resource-path>:
    +freshness:
      build_after: # Available only on dbt platform Enterprise tiers
        count: <positive_integer>
        period: minute | hour | day
        updates_on: any | all # optional, default is `any`
Property 파일

models/.yml

models:
  - name: stg_orders
    config:
      freshness:
        build_after:  # Available only on dbt platform Enterprise tiers
          count: <positive_integer>
          period: minute | hour | day
          updates_on: any | all # optional, default is `any`
SQL 파일 config

models/.sql

{{
    config(
      freshness={
        "build_after": {
          "count": <positive_integer>,
          "period": "minute" | "hour" | "day",
          "updates_on": "any" | "all"
        }
      }
    )
}}

build_after config는 현재 비권장인 상태 인지 오케스트레이션(SAO)에 적용돼요. build_after새 source 또는 업스트림 데이터가 있을 때만 모델을 재빌드해요. 이는 다른 모델에 의존하지만 주기적으로만 업데이트하면 되는 모델에 유용해요.

freshness는 dbt 작업 오케스트레이션과 함께 작동해, 예약된 작업에서 모델을 언제 재빌드해야 할지 결정하는 데 도움을 줘요. 작업이 실행되면 dbt는 모델이 필요한 경우에만 실행되도록 해, 모델이 불필요하게 과잉 빌드되는 것을 피해요. dbt는 이를 위해:

  • 모델에 새 데이터가 있는지 확인해요
  • countperiod를 기준으로 마지막 빌드 이후 충분한 시간이 지났는지 확인해요

sources와 업스트림 모델(mesh)의 경우 dbt는 커스텀 freshness 계산(구성된 경우)을 기준으로 데이터가 "새것"인지 판단해요. source의 freshness가 경고/오류 임계값을 지나면 dbt는 빌드 중에 경고/오류를 발생시켜요.

구성은 다음 부분으로 이뤄져요:

구성 설명
build_after dbt platform Enterprise 티어에서만 사용 가능해요. freshness 아래 중첩된 config예요. 모델이 마지막으로 빌드된 이후 지정된 count와 period가 지났는지를 기준으로, 새 데이터가 있을 때 모델을 재빌드할지 결정하는 데 사용해요. dbt는 작업이 실행될 때마다 새 데이터를 확인하지만, build_after는 충분한 시간이 지나고 새 데이터가 있을 때만 모델이 재빌드되도록 보장해요.
countperiod dbt가 새 데이터를 얼마나 자주 확인할지 지정해요. 예를 들어 count: 4, period: hour는 dbt가 4시간마다 확인한다는 뜻이에요. build_after를 구성할 때 countperiod가 모두 필요해요.
updates_on 선택이에요. 기본값은 any예요. 업스트림 데이터 변경이 언제 작업 빌드를 트리거할지 결정해요. 다음 값을 사용하세요:
- any (기본값): 어느 직접 업스트림 노드에 마지막 빌드 이후 새 데이터가 있으면 모델이 빌드돼요. 더 빠르지만 비용이 늘 수 있어요.
- all: 모든 직접 업스트림 노드에 마지막 빌드 이후 새 데이터가 있을 때만 모델이 빌드돼요. 비용이 적고 요구사항이 더 많아요.

dbt State를 사용한다면 build_after config가 freshness 블록 밖 state 블록으로 이동했어요:

상태 인지 오케스트레이션 dbt State
freshness.build_after.count + freshness.build_after.period state.lag_tolerance
freshness.build_after.updates_on state.require_fresh_data_from

자세한 내용은 상태 인지 오케스트레이션에서 마이그레이션을 참고하세요.

기본값

build_after 키의 기본값은:

build_after:
  count: 0
  period: minute
  updates_on: any

updates_on의 기본값은 any예요. 즉 기본적으로 모델은 새 데이터가 조금이라도 있으면 예약 작업이 실행될 때마다 빌드돼요.

예제

다음 예제는 모델을 더 자주, 덜 자주, 또는 커스텀 주기로 실행하도록 구성하는 방법을 보여줘요.

덜 자주

새 데이터가 있는 한 X 시간 간격보다 자주 빌드되지 않도록 구성하면(비용 절감) 덜 자주 실행되는 모델을 만들 수 있어요.

models:
  - name: stg_wizards
    config:
      freshness:
        build_after:
          count: 4
          period: hour
          updates_on: all
  - name: stg_worlds
    config:
      freshness:
        build_after:
          count: 4
          period: hour
          updates_on: all

상태 인지 오케스트레이션 작업이 트리거되면 dbt는 두 가지를 확인해요:

  • 모든 업스트림 모델에 새 source 데이터가 있는지
  • stg_wizardsstg_worlds 모델이 4시간 이상 전에 빌드되었는지

조건이 모두 충족되면 dbt가 모델을 빌드해요. 이 경우 updates_on: all config가 설정돼 있어요. raw.wizards source에 새 데이터가 있지만 stg_wizardsstg_worlds가 3시간 전에 마지막으로 빌드되었다면 아무것도 빌드되지 않아요.

더 자주

더 자주 실행되는 모델을 빌드하려면, 모든 의존성이 새 데이터를 기다리는 대신 어느 의존성에든 새 데이터가 생기면 바로 빌드되도록 구성할 수 있어요.

models:
  - name: stg_wizards
    config:
      freshness:
        build_after:
          count: 1
          period: hour
          updates_on: any
  - name: stg_worlds
    config:
      freshness:
        build_after:
          count: 1
          period: hour
          updates_on: any

상태 인지 오케스트레이션 작업이 실행되면 dbt는 두 가지를 확인해요:

  • 적어도 하나의 업스트림 모델에 새 source 데이터가 있는지
  • stg_wizardsstg_worlds가 지난 한 시간 안에 빌드되지 않았는지

조건이 모두 충족되면 dbt가 모델을 재빌드해요. 두 모델 모두 새 데이터가 없으면 아무것도 빌드되지 않아요.

이 예시에서 updates_on: any가 설정되어 있으므로, raw.wizards source에만 새 데이터가 있고 지난 한 시간 안에 stg_wizards만 빌드되었어도(stg_worlds는 업데이트되지 않았어도), dbt는 source 업데이트 하나와 적격(오래된) 모델 하나만 필요하므로 여전히 모델을 빌드해요.

커스텀 주기

build_after와 함께 커스텀 로직을 사용해 날짜별로 다른 주기를 설정하거나, 특정 기간(예: 주말)에 빌드를 건너뛸 수도 있어요.

Project 파일

dbt_project.yml

+freshness:
  build_after:
    # wait at least 48 hours before building again, if Saturday or Sunday
    # otherwise, wait at least 1 hour before building again
    count: "{{ 48 if modules.datetime.datetime.today().weekday() in (5, 6) else 1 }}"
    period: hour
    updates_on: any
SQL 파일 config

models/.sql

{{
    config(
      freshness={
        "build_after": {
          "count": 48 if modules.datetime.datetime.today().weekday() in (5, 6) else 1,
          "period": "hour",
          "updates_on": "any"
        }
      }
    )
}}

더 알아보기 (Learn more)