데이터 엔지니어 · AI 엔지니어를 위한 핵심 요약
dbt(Data Build Tool)는 데이터 웨어하우스 내에서 변환(Transformation) 작업을 버전 관리, 테스트, 문서화할 수 있게 해주는 개발 도구입니다. "ELT의 T"에 집중하며, 분석가와 엔지니어가 SQL 중심으로 데이터 파이프라인을 구축하고 운영할 수 있는 프레임워크를 제공합니다.
Python이나 Java가 아닌 SQL로 비즈니스 로직을 정의합니다. Jinja2 템플릿을 통해 반복문, 조건문, 매크로를 사용해 동적 SQL을 작성할 수 있습니다.
모든 모델(SQL 파일)과 설정이 코드로 관리되므로 Git을 통한 버전 관리, 코드 리뷰, CI/CD 파이프라인 적용이 자연스럽습니다.
선언적 테스트(not null, unique 등)와 사용자 정의 테스트를 쉽게 추가할 수 있으며, `dbt docs generate`로 데이터 계보(Lineage)와 문서를 자동 생성합니다.
dbt는 실행 엔진이 아닌, 변환 로직을 정의하고 관리하는 도구입니다. 실제 쿼리는 Snowflake, BigQuery, Redshift 등의 데이터 웨어하우스에서 실행됩니다.
기존의 데이터 변환 방식(스케줄러 + SQL 스크립트, Spark/Airflow Python 작업)과 dbt를 비교합니다.
| 방식 | 장점 | 단점 | dbt의 해결책 |
|---|---|---|---|
| 스케줄러 + SQL 파일 (Crontab, Airflow SQLOperator) |
간단한 설정, SQL만 알면 됨 | 의존성 관리 어려움, 테스트/문서화 부재, 코드 중복 | DAG 기반 의존성 자동 추론, 내장 테스트 프레임워크, Jinja 매크로로 재사용 |
| Spark/PySpark (Airflow, Databricks) |
대규모 데이터 처리, 복잡한 비즈니스 로직 가능 | 운영 복잡도 높음, SQL 분석가 참여 어려움, 클러스터 비용 | SQL 중심으로 분석가와 협업 용이, 웨어하우스 컴퓨팅만 사용 |
| 프로프라이etary ETL (Informatica, Talend) |
GUI 기반 개발, 통합 관리 | 비용 높음, 벤더 락인, 버전 관리 어려움 | 오픈소스, 코드 기반(Git), 클라우드 웨어하우스 네이티브 |
실시간 스트리밍 처리, 데이터 웨어하우스 외부의 데이터 소스 변환, 매우 복잡한 비정형 데이터 처리에는 다른 도구(Spark, Flink)를 고려해야 합니다.
dbt 프로젝트의 기본 구조와 주요 컴포넌트를 이해합니다.
my_dbt_project/
├── dbt_project.yml # 프로젝트 설정의 핵심
├── models/ # 분석 모델 (SQL + YAML)
│ ├── staging/ # 스테이징 레이어
│ ├── marts/ # 데이터 마트 (BI용)
│ └── schema.yml # 모델 정의, 테스트, 문서화
├── macros/ # 재사용 가능한 Jinja 매크로
├── tests/ # 사용자 정의 테스트 (SQL/Python)
├── snapshots/ # SCD Type2 스냅샷
└── seeds/ # 정적 CSV 데이터
Python 환경에서 dbt-core와 데이터 웨어하우스 어댑터를 설치합니다. (예: Snowflake)
pip install dbt-core dbt-snowflake # 프로젝트 생성 (현재 디렉토리에 생성) dbt init my_analytics_project
데이터 웨어하우스 연결 정보를 설정합니다.
my_analytics_project:
target: dev
outputs:
dev:
type: snowflake
account: your_account
user: your_user
password: "{{ env_var('DBT_PASSWORD') }}"
role: transformer
database: analytics
warehouse: transform_wh
schema: dbt_dev
threads: 4
models/staging/jaffle_shop/orders.sql 파일을 생성합니다.
{{ config(materialized='view') }}
with orders as (
select * from {{ source('jaffle_shop', 'orders') }}
),
final as (
select
id as order_id,
user_id as customer_id,
order_date,
status,
-- 매크로 사용 예시
{{ cents_to_dollars('amount') }} as amount_usd
from orders
where order_date >= '2024-01-01'
)
select * from final
models/staging/jaffle_shop/schema.yml 파일을 생성합니다.
version: 2
sources:
- name: jaffle_shop
database: raw
schema: jaffle_shop
tables:
- name: orders
loaded_at_field: _loaded_at
freshness:
warn_after: {count: 12, period: hour}
error_after: {count: 24, period: hour}
models:
- name: orders
description: "Cleaned and transformed orders table."
columns:
- name: order_id
description: "Primary key for orders."
tests:
- unique
- not_null
- name: customer_id
tests:
- relationships:
to: ref('customers')
field: customer_id
- name: status
tests:
- accepted_values:
values: ['placed', 'shipped', 'completed', 'returned']
# 모델 실행 (DAG 기반) dbt run --select orders # 특정 태그가 있는 모델만 실행 dbt run --select tag:staging # 테스트 실행 dbt test --models orders # 문서 생성 및 서버 실행 dbt docs generate dbt docs serve --port 8080
dbt_project.yml 파일의 주요 설정값들입니다.
| 설정 | 기본값 | 설명 | 실무 예시 |
|---|---|---|---|
name |
- | 프로젝트 이름 | name: my_analytics |
version |
1.0.0 | 프로젝트 버전 (SemVer) | version: '1.2.0' |
profile |
- | 프로필 이름 (profiles.yml과 매핑) | profile: my_analytics_project |
model-paths |
["models"] | 모델 파일 경로 | model-paths: ["models", "transformations"] |
seed-paths |
["seeds"] | 시드 파일 경로 | seed-paths: ["seeds", "static_data"] |
test-paths |
["tests"] | 테스트 파일 경로 | test-paths: ["tests", "custom_tests"] |
models |
{} | 모델별 설정 (머티리얼라이제이션, 태그 등) | models:
my_analytics:
staging:
materialized: view
tags: ["staging"]
marts:
materialized: table
tags: ["marts"] |
seeds |
{} | 시드 설정 (컬럼 타입 등) | seeds:
my_analytics:
country_codes:
column_types:
country_code: varchar(2)
country_name: varchar(100) |
vars |
{} | 전역 변수 (Jinja에서 사용) | vars: start_date: '2024-01-01' currency: 'USD' |
query-comment |
null | 실행 쿼리에 추가할 주석 | query-comment: "{{ node.name }}" |
원인: dbt는 ref() 함수를 통해 의존성을 자동 추론합니다. 명시적 의존성이 없으면 실행 순서가 보장되지 않습니다.
해결: dbt compile 후 target/compiled 디렉토리에서 생성된 SQL을 확인하세요. dbt ls --resource-type model --output name,depends_on으로 의존성 그래프를 확인합니다.
원인: 너무 많은 모델을 한 번에 실행, 웨어하우스 리소스 부족, 비효율적인 SQL.
해결:
dbt run --select model_name으로 특정 모델만 실행threads 수 조정 (너무 높으면 웨어하우스 병목)materialized: view로 설정하여 테이블 재생성 오버헤드 제거{{ config(materialized='incremental') }}원인: Jinja 구문 오류, 중괄호 불일치, 정의되지 않은 변수/매크로 참조.
해결: dbt compile로 컴파일 단계에서 에러를 먼저 확인합니다. 에러 메시지는 정확한 라인을 가리킵니다. {{ debug() }} 매크로로 변수 값을 출력해 디버깅합니다.
원인: dbt run이 기존 테이블을 드롭하고 재생성할 때 예기치 않은 오류 발생.
해결:
dbt run --dry-run으로 실행 계획 미리 확인 (일부 어댑터만 지원)materialized: incremental로 변경하여 전체 재생성 방지dbt run --target prod --defer --state path/to/artifactsunique_key와 incremental_strategy를 적절히 설정.{{ config(...) }} 블록 내에서 정의.tags: ["hourly", "finance"]로 모델을 그룹화하여 선택적 실행 가능.1. 공식 튜토리얼로 기본 개념 익히기 → 2. Jaffle Shop 클론하여 실습 → 3. 실제 데이터로 작은 프로젝트 시작(스테이징 레이어만) → 4. 매크로, 패키지 등 고급 기능 도입 → 5. CI/CD(예: GitHub Actions), 프로덕션 배포 전략 수립