왜 dbt인가 — SQL 폴더가 50개를 넘어가는 순간

데이터 엔지니어로 일하다 보면 누구나 겪는 장면이 있어요. 프로젝트 초반에는 SQL 파일이 4~5개라 그럴듯해 보입니다. 그런데 6개월만 지나면 폴더에 SQL 스크립트가 50개를 넘어가고, 이 시점부터는 어떤 스크립트가 어떤 순서로 돌아가는지 아무도 확신하지 못합니다.

  • 컬럼 하나 이름이 바뀌면 다운스트림 대시보드가 조용히 깨지고
  • 야간 1,000만 건 적재 잡이 실패하면 시스템 전체가 멈추고
  • 신규 입사자는 SQL 파일을 전부 열어보기 전까지 프로젝트를 이해하지 못합니다

전통적으로 SQL은 '고립된 스크립트 모음'으로 취급됐어요. dbt는 이 관점을 뒤집습니다. SQL을 소프트웨어 프로젝트의 구성 요소로 취급하는 거죠. 각 변환(transformation)은 명확한 목적을 갖고, 의존 관계는 코드로 정의됩니다.

이 글에서는 dbt Core(무료, Apache 2.0)와 DuckDB(MIT 라이선스)를 로컬에 세팅하고, 모델·소스·테스트·문서화까지 한 번에 훑어봅니다. 엔터프라이즈에서 Snowflake, BigQuery, Redshift, Databricks와 함께 쓰이는 도구지만, 로컬에서 똑같이 연습할 수 있어요. 근거자료는 Getting Started with dbt에서 확인할 수 있습니다.

Data engineer visualizing dbt transformation pipeline lineage graph on analytics dashboard Software Concept Art

설치부터 첫 모델 실행까지

1) 가상환경과 dbt 설치

프로젝트를 격리하기 위해 Python 가상환경부터 만듭니다. Python 3.13 기준으로 진행했어요.

# 프로젝트 폴더 생성 및 가상환경 활성화
mkdir dbt-demo
cd dbt-demo
python3 -m venv .venv
.\.venv\Scripts\Activate.ps1

# dbt-core와 duckdb 어댑터 설치 (DuckDB는 한 파일로 설치됩니다)
pip install dbt-duckdb

2) dbt 프로젝트 초기화

dbt init
# 프로젝트 이름: my_dbt_demo (하이픈 불가, 언더스코어 사용)
# DB 선택: 1 (duckdb)

dbt init이 끝나면 profiles.yml이 홈 디렉터리(C:\Users\<user>\.dbt\)에 생성됩니다. 이 파일이 DB 연결 정보를 들고 있어요. DuckDB 파일 경로를 프로젝트 폴더 안으로 옮겨봅시다.

# ~/.dbt/profiles.yml
my_dbt_demo:
  outputs:
    dev:
      type: duckdb
      path: "{{ env_var('USERPROFILE') }}/projects/dbt-demo/data/duckdb.dev"
      schema: raw
      threads: 1
    prod:
      type: duckdb
      path: "{{ env_var('USERPROFILE') }}/projects/dbt-demo/data/duckdb.prod"
      schema: raw
      threads: 4
  target: dev

3) 소스(source)와 모델(model) 정의

소스는 dbt가 만들지 않은, 이미 존재하는 원본 테이블입니다. 모델은 dbt가 실행할 SQL 조각으로, 새 테이블이나 뷰를 만듭니다. 둘을 YAML로 연결하면 테이블 이름이 바뀌어도 한 곳만 고치면 됩니다.

# models/orders.yml
version: 2
sources:
  - name: raw
    schema: raw
    tables:
      - name: orders
-- models/customer_orders_summary.sql
{{ config(materialized='table') }}

with completed_orders as (
    select
        order_id,
        customer_name,
        order_date,
        quantity,
        quantity * unit_price as order_value
    from {{ source('raw', 'orders') }}
    where lower(order_status) = 'completed'
)

select
    customer_name,
    count(*) as completed_order_count,
    sum(quantity) as total_units_purchased,
    round(sum(order_value), 2) as total_revenue,
    round(avg(order_value), 2) as average_order_value,
    min(order_date) as first_order_date,
    max(order_date) as most_recent_order_date
from completed_orders
group by customer_name

4) 실행과 검증

dbt run
# 1 of 1 OK created sql table model raw.customer_order_summary [OK in 0.11s]

DuckDB CLI로 결과를 확인해보면 고객별 완료 주문 요약 테이블이 정상적으로 생성된 걸 볼 수 있습니다. 테이블 하나 만드는데 이렇게까지 해야 하나 싶죠? 맞아요. 하지만 테이블이 수십, 수백 개가 되는 순간 이 투자는 반드시 돌아옵니다.

Local DuckDB database file connected to dbt Core CLI terminal on Windows developer laptop System Abstract Visual

테스트와 문서화 — dbt의 진짜 무기

데이터 품질 테스트

dbt는 YAML에 테스트를 선언하면 dbt build 또는 dbt test로 자동 검증합니다. 내장 테스트는 네 가지: unique, not_null, relationships, accepted_values.

# models/orders.yml (테스트 추가)
version: 2
sources:
  - name: raw
    schema: raw
    tables:
      - name: orders
        columns:
          - name: order_status
            data_tests:
              - accepted_values:
                  arguments:
                    values: ['completed', 'processing', 'returned', 'cancelled']

models:
  - name: customer_order_summary
    columns:
      - name: customer_name
        data_tests:
          - not_null
dbt test
# FAIL 1 not_null_customer_order_summary_customer_name
# FAIL 1 source_accepted_values_raw_orders_order_status__...

일부러 order_status를 invalid로 바꿔보면 테스트가 정확히 잡아냅니다. 모델이 실패해도 dbt는 테이블을 삭제하거나 롤백하지 않는다는 점은 주의하세요. 다운스트림 모델은 스킵되지만 원본 테이블은 그대로 남습니다.

자동 문서화와 리니지 그래프

dbt docs generate
dbt docs serve

브라우저에 프로젝트 문서와 리니지 그래프(모델 간 의존 관계 시각화)가 뜹니다. YAML에 description을 채워 넣을수록 문서 품질이 올라가요. 신규 팀원이 SQL 수백 개를 역공학하지 않아도 파이프라인 전체를 즉시 파악할 수 있다는 게 이 기능의 진짜 가치입니다.

주의사항과 한계

  • dbt는 변환 도구입니다. 적재(ingestion)나 오케스트레이션은 별도 도구(Airflow, Dagster 등)가 필요해요.
  • 러닝커브가 있습니다. Jinja, 매크로, 스냅샷까지 들어가면 학습 곡선이 가파릅니다.
  • 소규모 프로젝트에는 오버킬일 수 있습니다. 테이블 5개짜리 프로젝트라면 그냥 SQL 스크립트가 나을 수도 있어요.
  • 국내 SI 환경에서는 dbt Cloud 도입이 어려운 경우가 많아 dbt Core + 자체 오케스트레이션 조합이 현실적입니다. 폐쇄망에서는 DuckDB나 PostgreSQL 어댑터로 PoC부터 시작하는 걸 권합니다.

Python virtual environment terminal running dbt init command for new analytics project Algorithm Concept Visual

다음 단계 — 여기서 멈추면 반쪽짜리

이 글에서 다룬 건 dbt의 표면입니다. 실무에서 진짜 위력을 발휘하려면 다음 주제로 넘어가세요.

  1. Incremental models — 매번 전체 테이블을 재생성하지 않고 신규/변경 레코드만 처리
  2. Jinja 템플릿 — 변수, 조건문, 반복문으로 SQL을 프로그래밍 언어처럼 다루기
  3. Macros — 재사용 가능한 SQL 로직 조각, 파라미터화
  4. Snapshots — 원본 레코드의 시간에 따른 변화 이력 보존 (SCD Type 2)
  5. Packages — 다른 팀이 만든 모델·매크로·테스트를 그대로 가져다 쓰기

데이터 파이프라인을 코드로 관리하는 감각은 한 번 익히면 되돌아가기 어렵습니다. 로컬 DuckDB로 부담 없이 시작해보세요.

함께 보면 좋은 글

본 콘텐츠는 신뢰할 수 있는 출처를 바탕으로 AI 도구를 활용하여 초안이 작성되었으며, 편집자의 검토를 거쳐 발행되었습니다. 전문가의 조언을 대체하지 않습니다.