freshness
freshness
freshness config로 source 또는 model 데이터가 얼마나 신선해야 하는지 선언할 수 있어요. warn_after나 error_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 검사가 경고를 보고하는지 정의해요. count와 period가 모두 필요해요. |
error_after |
최신 데이터가 얼마나 오래되면 freshness 검사가 오류를 보고하는지 정의해요. warn_after와 같은 형식이에요. |
loaded_at_field |
dbt가 최근 로드된 타임스탬프를 파악하기 위해 조회하는 컬럼이에요. 어댑터 메타데이터를 사용할 수 없을 때 필요해요. |
loaded_at_query |
가장 최근 로드된 타임스탬프를 반환하는 SQL 표현식이에요. loaded_at_field의 대안이에요. 같은 리소스에 loaded_at_query와 loaded_at_field를 둘 다 설정하면 파싱 오류가 발생해요. dbt v1.10 이상에서 사용 가능해요. |
filter |
freshness 쿼리에 WHERE 절을 추가해 스캔할 데이터를 제한해요. BigQuery 파티션 테이블이나 Snowflake, Databricks, Spark의 대형 테이블에 유용해요. source나 model의 다른 용도에는 영향을 주지 않아요. loaded_at_query에는 적용되지 않아요. |
warn_after와 error_after 중 하나 또는 둘 다 제공할 수 있어요. 둘 다 설정하지 않으면 dbt는 해당 리소스의 freshness를 확인하지 않아요. warn_after와 error_after 각각은 count와 period가 모두 필요하며, 하나만 설정하면 파싱 경고가 발생하지만 dbt freshness 실행 시 오류가 돼요.
Source freshness
Project 파일
dbt_project.yml
sources:
<resource-path>:
+freshness:
warn_after:
count: <positive_integer>
period: minute | hour | day
Property 파일
models/
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에 설정한
freshness와loaded_at_field는 해당 source의 모든 테이블에 적용돼요. - source 테이블에 설정한
freshness와loaded_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/
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_after와error_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/
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/
{{
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 run과 dbt build는 경고만 하고 여전히 성공해요. 오직 dbt freshness만 이를 오류로 취급해요. 별도로, view와 external 모델은 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_b는 orders에 의존해요. 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/
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/
{{
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는 이를 위해:
- 모델에 새 데이터가 있는지 확인해요
count와period를 기준으로 마지막 빌드 이후 충분한 시간이 지났는지 확인해요
sources와 업스트림 모델(mesh)의 경우 dbt는 커스텀 freshness 계산(구성된 경우)을 기준으로 데이터가 "새것"인지 판단해요. source의 freshness가 경고/오류 임계값을 지나면 dbt는 빌드 중에 경고/오류를 발생시켜요.
구성은 다음 부분으로 이뤄져요:
| 구성 | 설명 |
|---|---|
build_after |
dbt platform Enterprise 티어에서만 사용 가능해요. freshness 아래 중첩된 config예요. 모델이 마지막으로 빌드된 이후 지정된 count와 period가 지났는지를 기준으로, 새 데이터가 있을 때 모델을 재빌드할지 결정하는 데 사용해요. dbt는 작업이 실행될 때마다 새 데이터를 확인하지만, build_after는 충분한 시간이 지나고 새 데이터가 있을 때만 모델이 재빌드되도록 보장해요. |
count와 period |
dbt가 새 데이터를 얼마나 자주 확인할지 지정해요. 예를 들어 count: 4, period: hour는 dbt가 4시간마다 확인한다는 뜻이에요. build_after를 구성할 때 count와 period가 모두 필요해요. |
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_wizards와stg_worlds모델이 4시간 이상 전에 빌드되었는지
두 조건이 모두 충족되면 dbt가 모델을 빌드해요. 이 경우 updates_on: all config가 설정돼 있어요. raw.wizards source에 새 데이터가 있지만 stg_wizards와 stg_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_wizards나stg_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/
{{
config(
freshness={
"build_after": {
"count": 48 if modules.datetime.datetime.today().weekday() in (5, 6) else 1,
"period": "hour",
"updates_on": "any"
}
}
)
}}