지연

지연 (Defer)

defer는 샌드박스 환경에서 업스트림 부모를 먼저 빌드하지 않고도 프로젝트의 일부 모델/테스트/함수를 실행할 수 있게 해주는 강력한 기능이에요. 프로덕션 매니페스트를 참조해 아직 빌드되지 않은 상위 리소스를 해석하는 방법을 다뤄요.

출처: 문서

본문

defer는 샌드박스 환경에서 (dbt v1.11 이상) 모델, 테스트, 함수의 일부를 업스트림 부모를 먼저 빌드하지 않고 실행할 수 있게 해주는 강력한 기능이에요. 이는 대규모 프로젝트에서 소수의 모델을 테스트할 때 시간과 컴퓨팅 리소스를 절약할 수 있어요.

파이프라인 끝의 모델을 수정할 때 모든 것을 업스트림에서 실행하는 대신 프로덕션 모델을 가리켜 수정하려면 'defer'를 사용해요.

defer는 이전 dbt 실행의 매니페스트를 필요로 해요. --state 플래그로 경로를 제공하거나 (dbt v1.11 이상) DBT_ENGINE_STATE 환경 변수를 설정해요. state: 선택 방법과 함께 이 기능들은 "Slim CI"를 가능하게 해요. state에 대해 더 읽어 보세요.

일부 사용 사례에서는 dbt clone을 사용해 비슷한 기능을 달성할 수 있어요. 자세한 내용은 clone을 참고하세요.

state:modified--defer에 별도의 state를 사용하는 것이 가능해요. --state/(dbt v1.11 이상) DBT_ENGINE_STATE--defer-state/(dbt v1.11 이상) DBT_ENGINE_DEFER_STATE에 각각 다른 매니페스트 경로를 전달하면 되요. 이렇게 하면 다음을 원할 때 더 세밀한 제어가 가능해요:

  • 한 환경이나 과거 시점의 논리적 상태와 비교
  • 다른 환경이나 시점의 적용된 상태로 defer

--defer-state가 지정되지 않으면 deferral은 --state에 제공된 매니페스트를 사용해요. 대부분의 경우 두 곳에 같은 state를 사용하기를 원할 거예요. 프로덕션과 논리적 변경을 비교하고, 빌드되지 않은 업스트림 리소스에 대해서는 프로덕션 환경으로 "페일오버"하세요.

사용법 (Usage)

dbt run --select [...] --defer --state path/to/artifacts
dbt test --select [...] --defer --state path/to/artifacts

기본적으로 dbt는 ref 호출을 해석할 때 target 네임스페이스를 사용해요.

--defer가 활성화되면 dbt는 (dbt v1.11 이상) ref와 function 호출을 state 매니페스트를 사용해 해석하는데, 다음 조건일 때만 그래요:

  • 해당 노드가 선택된 노드에 포함되지 않고,
  • 데이터베이스에 존재하지 않거나(또는 --favor-state가 사용된 경우).

임시(ephemeral) 모델은 다른 ref 호출의 "패스스루" 역할을 하므로 절대 defer되지 않아요.

(dbt v1.11 이상) {{ function('...') }}으로 참조되는 사용자 정의 함수(UDF)는 같은 조건에서 defer돼요. defer될 때 function()은 UDF가 현재 target에서 선택되지 않았거나 빌드되지 않았다면 state 매니페스트의 함수 정의로 해석돼요.

info — defer를 사용할 때 프로덕션 데이터셋, 개발 데이터셋, 또는 둘의 혼합에서 선택할 수 있어요. 이는 예기치 않은 결과를 낼 수 있음을 유의해요:

  • 개발에는 환경별 제한을 적용하는데 프로덕션에는 적용하지 않으면 예상보다 많은 데이터를 선택할 수 있어요.
  • 여러 부모에 의존하는 테스트(예: relationships)가 환경을 가로질러 실행될 수 있어요.

deferral은 --defer--state를 모두 설정해야 해요. 플래그를 명시적으로 전달하거나 환경 변수((dbt v1.11 이상) DBT_ENGINE_DEFERDBT_ENGINE_STATE)를 설정해요. Continuous integration에 대한 자세한 내용은 해당 문서를 참고하세요.

상태 선호 (Favor state)

--favor-state가 전달되면 dbt는 --state 디렉터리의 노드 정의를 우선시해요. 그러나 노드가 선택된 노드에도 포함된다면 이는 적용되지 않아요.

예시 (Example)

로컬 개발 환경에서 모든 모델을 target 스키마인 dev_alice에 만들어요. 프로덕션에서는 같은 모델이 prod라는 스키마에 만들어져요.

프로덕션 실행에서 dbt가 생성한 아티팩트(특히 manifest.json)에 접근해서 prod-run-artifacts라는 로컬 디렉터리에 복사해요.

run

model_b 작업 중이에요:

models/model_b.sql

select
    id,
    count(*)
from {{ ref('model_a') }}
group by 1

변경 사항을 테스트하고 싶어요. 개발 스키마 dev_alice에는 아무것도 없어요.

Standard run

dbt run --select "model_b"

target/run/my_project/model_b.sql

create or replace view dev_me.model_b as (
    select
        id,
        count(*)
    from dev_alice.model_a
    group by 1)

이전에 model_a를 이 개발 환경에서 실행하지 않았다면 dev_alice.model_a는 존재하지 않아 데이터베이스 오류가 발생해요.

Deferred run

dbt run --select "model_b" --defer --state prod-run-artifacts

target/run/my_project/model_b.sql

create or replace view dev_me.model_b as (
    select
        id,
        count(*)
    from prod.model_a
    group by 1)

model_a는 선택되지 않았으므로 dbt는 dev_alice.model_a가 존재하는지 확인해요. 존재하지 않으면 dbt는 {{ ref('model_a') }}의 모든 인스턴스를 prod.model_a로 대신 해석해요.

test

또한 model_amodel_b 사이의 참조 무결성을 확립하는 relationships 테스트가 있어요:

models/resources.yml

models:
  - name: model_b
    columns:
      - name: id
        data_tests:
          - relationships:
              arguments: # available in v1.10.5 and higher. Older versions can set the <argument_name> as the top-level property.
                to: ref('model_a')
                field: id

(이는 단순화된 예시예요. model_b의 모든 데이터는 이미 model_a에서 오기 때문이에요)

Without defer

dbt test --select "model_b"

target/compiled/.../relationships_model_b_id__id__ref_model_a_.sql

select count(*) as validation_errors
from (
    select id as id from dev_alice.model_b
) as child
left join (
    select id as id from dev_alice.model_a
) as parent on parent.id = child.id
where child.id is not null
  and parent.id is null

relationships 테스트는 model_amodel_b를 모두 필요로 해요. 이전 dbt run에서 model_a를 빌드하지 않았으므로 dev_alice.model_a가 존재하지 않고 이 테스트 쿼리는 실패해요.

With defer

dbt test --select "model_b" --defer --state prod-run-artifacts

target/compiled/.../relationships_model_b_id__id__ref_model_a_.sql

select count(*) as validation_errors
from (
    select id as id from dev_alice.model_b
) as child
left join (
    select id as id from prod.model_a
) as parent on parent.id = child.id
where child.id is not null
  and parent.id is null

dbt는 dev_alice.model_a가 존재하는지 확인해요. 존재하지 않으면 dbt는 schema 테스트에 있는 것을 포함해 {{ ref('model_a') }}의 모든 인스턴스를 prod.model_a를 사용하도록 해석해요. 쿼리는 성공해요. 환경을 가로질러 참조 무결성을 실제로 테스트하고 싶은지는 다른 문제예요.

더 알아보기 (Learn more)