< dbt 란 >
SQL 기반 데이터 변환 도구.
ETL 의 Transformation 을 담당
dbt 로 만들어진 (데이터 처리를 위한) SQL은 재사용이 가능하고
dependency 관리가 수월하다는 것이 큰 장점임
또한 dbt 는 테스트 기능 역시 제공하며
문서화 및 배포까지 도와줌
dbt 는 데이터 모델링 작업을 위한 데이터 처리를 하는 데 많이 사용됨
< dbt 알아야 하는 개념들 >
- model
- ref()
- incremental model, table, view
- macro, jinja
- snapshot
- source(), source freshness
- seed
- materialization
- dependency
- materialized : table, view
- test - schema test, singular test, custom test, whatnot
- dbt core - 오픈소스, CLI 기반, 로컬 개발
- dbt cloud - SaaS, UI 제공, 스케줄링, 협업 기능
< dbt project 디렉토리 구조 gi>
| dbt_project/ ├── models/ │ ├── staging/ | ├── stg_****.sql | └── schema.yml │ ├── intermediate/ | ├── int_****.sql | └── schema.yml │ └── marts/ | ├── fact_****.sql | ├── dim_****.sql | └── schema.yml │ ├── seeds/ ├── snapshots/ ├── macros/ ├── tests/ ├── analyses/ ├── snapshots/ │ ├── dbt_project.yml ├── packages.yml └── profiles.yml |
< models >
dbt 는 주로 raw 데이터를 staging 에 적재하고, intermediate, marts 과정을 거쳐 데이터를 처리하게 됨
이건 차원 모델링이나 메달리온 아키텍처와도 연관됨
models 내에 transformation 작업을 진행하는 SQL 이 들어있음
models 내에 SQL 파일들이 실행되면 나중에
table 이 되거나
view 가 되거나
incremental table 이 됨
그리고 dbt 가 알아서 dependency graph 를 그린다고 함
models/staging 단계에서는 최소한의 transformation 작업이 진행됨
- 컬럼명 정리
- 타입 정리(캐스팅)
- null 처리
원본 데이터를 최대한 파괴하지 않는 선에서 작업 진행. lake 로 사용할 수 있도록
models/intermediate 단계에서는 복잡한 transformation 을 분리하는 작업이 진행됨
말 그대로 중간 계산 과정을 만드는 단계
- join
- aggregation
- business logic
- reusable logic
등의 처리를 진행하게 됨
예를 들어
select
customer_id,
count(*) as total_orders,
sum(amount) as lifetime_value
from {{ ref('stg_orders') }}
group by 1
데이터 처리를 위해 엄청 복잡하고 방대한 SQL 이 실행되어야 하는 경우,
이걸 한 번에 다 처리하면 디버깅하기도 어렵고 재사용하기도 어렵고 이해하기도 어려움
따라서 intermediate 과정을 중간에 넣어서, 중간 생산물들을 처리하도록 하는 것
나중에 최종 테이블 만들기 위한 transformation 작업이 복잡해지지 않고
중간결과물 재사용도 가능하도록
models/marts 단계에서는 최종 비즈니스 테이블을 만드는 작업이 진행됨
여기 만들어진 테이블은 BI, analytics teams 가 보고 비즈니스 결정하는 데 사용함
차원모델링의 최종 결과 테이블들(fact, dim tables) 가 이 단계에서 생성됨
아래와 같이, 도메인별로 각기 다른 차원 테이블들을 만들어주면 도메인별로 분석하기가 편해짐
팀 단위의 ownership 을 구분하고 결정하기도 쉬워짐
| models/ └── marts/ ├── finance/ │ ├── fct_payments.sql │ └── dim_customers.sql │ └── marketing/ ├── fct_campaigns.sql └── dim_channels.sql |
그래서 models 의 전체적인 flow 를 보면 아래와 같게 됨
| raw_orders ↓ stg_orders ↓ int_customer_orders ↓ fct_customer_revenue |
dbt run 하면 dbt 가 위와 같은 dependency graph 를 자동으로 생성해서 처리한다고 함
(=stage 건너뛰고 marts 먼저 만드는 등 순서를 뒤죽박죽 섞어서 실행하지 않는다는 말)
그럼 models 마다 존재하는 schema.yml 은 뭐냐?
shecma.yml 은
- test 를 정의하는 역할
- documentation 만드는 역할
- column 을 설명하는 역할
아래와 같은 schema.yml 이 있다고 해보자
| version: 2 models: - name: stg_orders columns: - name: order_id tests: - not_null - unique |
models.columns.tests 항목을 통해, 해당 컬럼의 test 를 설정할 수 있음
예를 들어 null 이 존재하는지 확인하는 not_null,
중복이 없는지 확인하는 unique 등등
< macros >
재사용 가능한 SQL 함수를 정의해 두는 곳
말 그대로, 진짜 함수(파라미터 받아서 결과 반환하는)를 만들어 두는 곳임
예를 들어, 아래와 같은 macro(generate_surrogate_key.sql)가 존재하고 내용도 아래와 같다고 하자
| macros/ └── generate_surrogate_key.sql |
| {% macro cents_to_dollars(column_name) %} {{ column_name }} / 100.0 {% endmacro %} |
함수 이름은 cents_to_dollars
함수의 파라미터는 column_name
아래와 같이 models 내 SQL에서 불러 사용할 수 있음
| {{ cents_to_dollars('amount') }} |
왜 macro 를 사용해야 하나?
바로 코드 중복을 제거해줄 수 있기 때문에
동일한 코드를 다양한 곳에서 똑같이 쓸 필요가 없고
또 macro 하나만 수정하면 나머지에 전부 적용되기 때문에 관리하기도 편함
< dbt_project.yml >
이 파일 내부에는 프로젝트의 핵심 설정 파일이 들어있음
dbt 프로젝트 전체의 동작 방식을 정의하는 중앙 설정 파일임.
(Docker Compose의 docker-compose.yml 같은 거라고 보면 된대!)
만약 내가 아래와 같은 sql 을 만들고 dbt run 을 하면
| models/staging/stg_orders.sql |
| select * from raw.orders |
dbt 는 이 sql 을 실행하기 위해 갖가지 정보가 필요함
- 테이블 어디에 만들지?
- view 로 만들어야 하나? table 로 만들어야 하나? 아니면 incremental?
- 어떤 schema 에 만들지?
- 어떤 db 에 만들지? 등등
이런 정보들을 제공하는 곳이 바로 dbt_project.yml 임
dbt_project.yml 파트별로 예를 들어봄
| name: my_project version: '1.0' config-version: 2 |
name 은 dbt 프로젝트의 이름임
dbt init my_project 명령어를 실행해서 만들어졌을 것임
version 은 프로젝트의 버전. CI/CD 에서 사용된다고 함
config-version 은 설정파일 포맷 버전인데, 2로 고정됨. 왜 넣는지는 모르겠음
| model-paths: ["models"] seed-paths: ["seeds"] snapshot-paths: ["snapshots"] macro-paths: ["macros"] test-paths: ["tests"] |
위와 같은 config 를 줬기 때문에 dbt 가 어느 디렉터리에서 정보를 가져가야 할 지 알게 됨
이를테면, model-path: ["models"] 로 설정된 부분으로 인해, dbt 는 "model 들은 'models' 디렉터리 안에 있구나" 라고 인식함
| models: my_project: staging: +materialized: view +schema: staging intermediate: +materialized: ephemeral marts: +materialized: table +schema: marts |
위 config 의 'models'는 'model 에 대한 설정'을 의미함
models 이하에 나오는 config 를 통해
각 models(SQL 파일들)을 어떻게 처리해야 할 지 알 수 있음
my_project 는 프로젝트 이름
위의 name: my_project 와 연결됨
(프로젝트 이름을 굳이 다시 넣었다는 말은.. dbt_project.yml 내에 여러 프로젝트의 config 가 들어갈 수 있다는 말인가?)
materialized 가 뭐하는거야?
SQL 결과를 warehouse 에 어떤 형태로 저장할지 결정하는 부분
SQL 을 실행한 후 결과를 table 로 만들 수 있고, view 로 만들 수 있고, 혹은 incremental table 로 만들 수 있음
잘 보면, 각각의 SQL 파일들마다 materialized 가 있는게 아니고, 각 단계(staging, intermediate, marts)마다 materialized 가 존재함
즉, materialized 는 하나의 단계 전체적으로 적용된다고 볼 수 있음. 내가 이해한 게 맞나?
+materialized : view
여기서 view 는 실제 데이터를 저장하지 않는 가상 테이블임.
create view 명령어 실행되어 생성되는 뷰 그거 맞음
query 실행되는 런타임마다 그때 그때 계산되어 생성 및 사용되기 때문에, 항상 최신 데이터를 갖고 있음
하지만 매 런타임마다 계산되기 때문에, select 퍼포먼스가 느림
실제 테이블을 만들지 않기 때문에 차지하는 storage 적음
심지어 view 는 BI tool 에서도 접근이 가능함. BI tool 이 view 테이블에 접근할 때마다 view 쿼리를 계속 재실행한다는 말이지.....
view 는 staging 처리할 때 자주 사용된다고 하는데.. 그 이유가, 굳이 물리적인 테이블을 만들 필요가 없어서..?
staging 을 위한 sql 이 비용이 적은 이유(타입 캐스팅 혹은 컬럼명 변경 등)는 알겠어
그래서 view 처럼 런타임에 실행해도 무리가 없다는 거지
근데 staging 은 ground truth 역할(lake 마냥)을 해야하기 때문에 무조건 물리적인 테이블로 만들어둬야 하지 않나?
+materialized : table
여기서 table 은 실제 데이터를 저장하는 물리적인 테이블을 의미함
따라서 storage 용량을 차지하며 생성될 때 갖고있는 데이터를 그대로 보관하게 되며
하지만 view 와 달리 select 실행시 재계산을 하지 않아도 되기 때문에 select 퍼포먼스가 빠름
+materialized : incremental
변경분만 table 에 업데이트 함. hudi 의 upsert 와 같은 느낌인거지
즉, 기존 테이블이 없으면 새로운 table 을 만들고
기존 테이블에 데이터가 없으면 새로 데이터를 추가하고
기존 테이블에 데이터가 있으면 변경된 부분만 업데이트 함
incremental 이 적용되는 model 쿼리를 한 번 보자
| select * from orders where updated_at > ( select max(updated_at) from {{ this }} ) |
위와 같이 {{ this }} 를 통해 자기 자신 테이블에서 최신 날짜만 가져온 후
최신 날짜 이후로 들어온 데이터만 추출해서 incremental 로 업데이트
+materialized : ephemeral
실제 table이나 view 는 생성하지 않지만
SQL 내부에 inline 된다고....(????)
ephemeral 로 설정된 model SQL 을 실행해도
warehouse 내에 object 를 생성하지 않음(그건 view 도 마찬가지잖아?-> 개발자 입장에서는 view 도 object를 만들지 않는것 처럼 보이는데, 실제로 warehouse 입장에서는 object 처럼 보인다고 함. 그래서 select * from my_view 같이 from 에 view 를 넣은 쿼리가 가능함)
ephemeral 로 설정된 model SQL 은 downstream SQL 에서 CTE 처럼 삽입된다고 함
view 랑 다른게 뭐지...??
위의 예제에서 intermediate 부분에 ephemeral 이 설정되어있고,
marts 에 table 이 설정되어 있잖아
intermediate 및 marts SQL model 이 아래와 같다고 하자
| -- int_orders.sql select customer_id, sum(amount) as total_amount from {{ ref('stg_orders') }} group by 1 |
| -- fct_customer_revenue.sql select * from {{ ref('int_orders') }} where total_amount > 100 |
이걸 실행시키면, ephemeral 로 설정된 int_orders 의 쿼리가 fct_customer_revenue 쿼리 안으로 쏙 들어감
| select * from ( select customer_id, sum(amount) as total_amount from stg_orders group by 1 ) as int_orders where total_amount > 100 |
이걸 보고 inline 이 된다고 한 거임....
view 랑 ephemeral 의 차이는 알았는데, 그럼 각각은 무슨 장단점이 있을까?
- view 는 warehouse 에서 조회가능한 object를 주르륵 만들기 때문에, 여러개의 view 를 만들면 지저분해질 수 있음
ephemeral 은 지저분하지 않음(....이게 장점인가?)
- ephemeral 은 object 생성이 어려우니 디버깅하기가 어려움.
즉, view 처럼 select 해서 직접 값을 볼 수 없으니 ephemeral 로직이 잘 실행되었는지 아닌지 몰라
- ephemeral 을 사용하면 하나의 큰 nested sql 이 생성됨. 이게 단점이 되나?
- downstream 에서 동일한 ephemeral 을 여러번 사용하면, 중복 계산이 되기 때문에 퍼포먼스가 좋지 않음
그럼 ephemeral 은 언제 쓰는거야?
warehouse object 를 만들 가치가 없는 경우에 쓴다고 함(....이게 뭔 소리임)
너무 작은 크기의 view 를 만들 바에야 ephemeral 을 사용한다는 말인 듯
< seeds >
이곳에 csv 파일을 넣어두면, dbt 가 warehouse 에 자동으로 upload 한다고 함
작은 reference table 을 만드는 용도로 사용한다고 함
예를 들어 아래와 같은 csv 를 파일을 넣어두면....
| country_codes.csv currency_codes.csv |
무슨 일이 벌어지지? 어느 db 에 만드는거야? 함부러 table 만드는 권한은 어떻게 갖고있는거지?
< tests >
custom test 를 저장하는 곳
예를 들어 아래와 같이 내가 원하는 테스트를 만들어 넣음
| tests/ └── assert_positive_revenue.sql |
| select * from {{ ref('fct_orders') }} where revenue < 0 |
결과가 나타난다면, 실패하는 거임...
select * 의 결과가 나타나지 말아야 함
ref('fct_orders') 는 fact table 대상으로 테스트를 진행한다는 말이 될 것 같음
그럼 여기 tests 디렉터리 내에 있는 테스트와, schema.yml 내에 있는 테스트의 차이는?
< snapshots >
변경 이력 추적용이라는데 뭔 소린지 모르겠음
< analyses >
ad-hoc SQL 을 저장하는 곳이라는데 뭔 소린지 모르겠음
'Coding Interview' 카테고리의 다른 글
| [IT] Data Warehouse + Data Modeling 공부 (1) | 2026.04.16 |
|---|---|
| [Spark] interview questions (0) | 2026.01.22 |
| [Data Engineer] interview questions (0) | 2026.01.16 |
| [Airflow] interview questions (0) | 2025.12.14 |
| [SQL] interview questions (0) | 2025.12.12 |