dbt
고친 사람 github-actions[bot]
dbt 는 분석 전용 데이터베이스인 데이터 웨어하우스 안에서 원본 데이터를 분석용 표로 바꾸는 일을 맡습니다. 개발자는 만들 표마다 SELECT 조회문 하나를 파일로 적습니다. dbt 는 파일들이 서로 어느 표를 읽는지 보고 알맞은 순서로 웨어하우스에서 돌립니다. 만든 표가 기대한 모양인지 테스트하는 일도 합니다.
쉽고 빠른 이해
무슨 일을 하나 — 분석용 저장소 안에서 원본 표를 다듬어 새 표를 만드는 조회문들을 관리합니다. 예를 들어 주문 원본 표를 정리한 표를 만들고, 그 표로 고객별 매출 표를 다시 만듭니다.
왜 쓰나 — 조회문 파일이 수십 개로 늘면 어느 것을 먼저 돌릴지 사람이 기억해야 합니다. 표 이름을 파일마다 직접 적어 두면 이름 하나 바꿀 때 여러 파일을 고쳐야 합니다. 결과 표가 틀려도 아무도 모르고 지나갑니다.
어떻게 도나
- 표 하나마다 SELECT 문 하나를 파일로 씁니다. 다른 표는 이름 대신 함수로 가리킵니다
- 명령을 내리면 dbt 가 그 가리킴을 따라 순서를 정하고 저장소에 표를 만들게 합니다
- 설정 파일에 적어 둔 테스트를 돌려 값이 비었거나 겹친 곳을 찾습니다
대가 — 계산은 전부 저장소가 하므로 그 비용이 저장소 요금으로 나옵니다. 데이터를 꺼내 오고 넣는 일은 하지 않아서 다른 도구가 따로 있어야 합니다.
상세
dbt 는 이름을 data build tool 의 머리글자에서 따왔고 소문자로 적습니다. ELT(Extract Load Transform, 추출·적재·변환)의 세 단계 가운데 마지막 변환만 맡는 도구입니다.
이 절은 쇼핑몰의 주문 데이터를 예로 듭니다. 원본 주문 표를 정리한 표를 만들고, 그 표로 고객별 매출 표를 만드는 과정을 따라갑니다. 그 과정에서 dbt 의 부품인 모델, 가리키는 함수, 표를 만드는 방식, 테스트, 명령을 차례로 봅니다.
dbt 가 맡는 단계
데이터 웨어하우스는 여러 서비스의 데이터를 모아 분석 조회를 빠르게 돌리는 분석 전용 데이터베이스입니다. ELT 는 데이터를 원천에서 꺼내(추출) 손대지 않고 웨어하우스에 넣습니다(적재). 그다음 웨어하우스 안에서 분석에 맞게 다듬습니다(변환).
dbt 는 이 가운데 변환만 합니다. 데이터를 꺼내 오거나 넣는 일은 하지 않습니다. 원본 데이터는 다른 도구가 미리 웨어하우스에 넣어 두어야 합니다.
계산은 웨어하우스가 합니다. dbt 는 SQL(Structured Query Language, 구조화 질의 언어) 조회문을 만들어 웨어하우스에 보내기만 합니다. 그래서 dbt 를 돌리는 컴퓨터는 작아도 됩니다.
dbt 없이 SQL 파일을 쌓을 때
변환 작업은 처음엔 SQL 파일 몇 개로 시작합니다. 원본 주문 표를 정리하는 파일, 그 결과로 매출을 모으는 파일 같은 것입니다. 파일이 수십 개로 늘면 세 가지가 곤란해집니다.
첫째는 실행 순서입니다. 매출 표를 만드는 파일은 정리된 주문 표가 먼저 있어야 돕니다. 이런 앞뒤 관계를 사람이 기억해서 스크립트에 순서대로 적어야 합니다. 파일이 하나 늘 때마다 그 순서를 다시 맞춥니다.
둘째는 표 이름입니다. 파일마다 analytics.stg_orders 처럼 스키마와 표 이름을 직접 적습니다. 스키마는 데이터베이스 안에서 표들을 묶는 이름 공간입니다.
개발용 스키마에서 돌려 보려면 모든 파일의 이름을 바꿔야 합니다.
셋째는 검증입니다. 원본에 같은 주문이 두 번 들어와도 SQL 은 멈추지 않고 결과를 냅니다. 매출이 두 배로 잡힌 표가 아무 경고 없이 보고서로 넘어갑니다.
dbt 는 이 셋을 각각 받아 줍니다. 순서는 표 사이의 가리킴에서 저절로 정합니다. 이름은 실행 환경에 맞춰 바꿔 넣습니다. 검증은 테스트로 돌립니다.
모델
모델은 dbt 가 만들 표 하나입니다. 모델은 SELECT 문 하나만 담은 .sql 파일입니다.
파일 이름이 곧 만들어질 표의 이름이 됩니다.
개발자는 SELECT 만 씁니다. dbt 가 그 앞에 CREATE VIEW ... AS 나 CREATE TABLE ... AS 같은 문장을 붙여 웨어하우스에 보냅니다.
뷰로 만들지 테이블로 만들지는 뒤의 「표를 만드는 방식」 소절에서 봅니다. 개발자는 어떤 값을 담을지만 적습니다.
아래는 stg_orders.sql 이라는 모델입니다. 원본 주문 표에서 쓸 열만 고르고 이름과 값을 다듬습니다.
select
id as order_id,
customer_id,
lower(status) as status,
amount
from {{ source('app', 'orders') }}
마지막 줄의 중괄호 두 겹은 SQL 이 아닙니다. 템플릿 엔진은 글 속에 표시해 둔 곳을 실제 값으로 바꿔 넣는 도구입니다. 중괄호 두 겹은 Jinja 라는 템플릿 엔진의 표시입니다.
dbt 는 SQL 을 웨어하우스에 보내기 전에 이 표시를 먼저 풀어 평범한 SQL 로 만듭니다. 이 과정이 컴파일입니다.
모델 이름 앞의 stg_ 는 스테이징(staging)의 줄임입니다. 원본을 가볍게 정리만 한 모델에 붙이는 흔한 관례입니다. dbt 가 강제하지는 않습니다.
스테이징 모델들을 모아 보고서가 바로 읽을 표로 만든 것은 마트입니다. 뒤에 나올 고객별 매출 표가 마트입니다.
source 와 ref
위 모델의 source('app', 'orders') 는 원본 표를 가리키는 함수입니다. 원본 표는 dbt 가 만든 것이 아니라 적재 도구가 넣어 둔 것입니다.
YAML(YAML Ain't Markup Language) 은 들여쓰기로 계층을 나타내는 설정 파일 형식입니다. 어느 스키마에 어떤 원본 표가 있는지는 YAML 파일에 한 번 적어 둡니다.
sources:
- name: app
schema: raw
tables:
- name: orders
- name: customers
이 선언은 app 이라는 원본 묶음이 웨어하우스의 raw 스키마에 있다는 뜻입니다. 그 안에 orders 와 customers 표가 있습니다.
모델이 다른 모델을 읽을 때는 ref 함수를 씁니다. ref('stg_orders') 는 「stg_orders 모델이 만든 표」를 가리킵니다.
두 함수는 컴파일 때 실제 표 이름으로 바뀝니다. 오른쪽 주석이 바뀐 결과입니다.
{{ source('app', 'orders') }} -- raw.orders
{{ ref('stg_orders') }} -- dev.stg_orders
ref 가 바꿔 넣는 스키마는 실행 환경에 따라 달라집니다. 개발할 때는 dev 에, 운영에 올릴 때는 analytics 에 표를 만들게 할 수 있습니다.
모델 파일은 한 글자도 안 고칩니다. 앞 소절의 둘째 문제가 이렇게 풀립니다.
ref 가 정하는 실행 순서
ref 에는 쓰임이 하나 더 있습니다. dbt 는 모든 모델 파일을 읽어 어느 모델이 어느 모델을 ref 로 부르는지 모읍니다.
그 관계를 모으면 DAG(Directed Acyclic Graph, 방향 비순환 그래프)가 됩니다.
DAG 는 화살표가 한 방향으로만 이어지고 되돌아오는 고리가 없는 그래프입니다. 고리가 없으니 「누가 먼저인가」가 언제나 하나로 정해집니다. 쇼핑몰 예의 모델 셋을 그리면 아래와 같습니다.
flowchart TD
subgraph 원본 표
O["raw.orders"]
C["raw.customers"]
end
subgraph 스테이징 모델
SO["stg_orders"]
SC["stg_customers"]
end
subgraph 마트 모델
R["customer_revenue"]
end
O --> SO
C --> SC
SO --> R
SC --> R
customer_revenue 는 고객별 매출을 모으는 모델입니다. 파일 안에서 ref('stg_orders') 와 ref('stg_customers') 를 부릅니다.
그래서 dbt 는 두 스테이징 모델을 먼저 만들고 customer_revenue 를 나중에 만듭니다.
서로 기대지 않는 모델은 동시에 돌려도 됩니다. 위 그림의 두 스테이징 모델이 그렇습니다. dbt 는 이런 모델을 여러 개 나란히 웨어하우스에 보냅니다.
모델끼리 서로를 ref 로 부르면 고리가 생깁니다. 그러면 순서를 정할 수 없어서 dbt 는 실행 전에 오류를 내고 멈춥니다.
표를 만드는 방식
같은 SELECT 문이라도 웨어하우스에 남기는 방식은 여럿입니다. dbt 는 이 방식을 머티리얼라이제이션(materialization)이라고 부릅니다. 「조회문의 결과를 실물로 만드는 방식」이라는 뜻입니다. 모델마다 따로 고를 수 있습니다.
| 방식 | 웨어하우스에 남는 것 | 알맞은 경우 |
|---|---|---|
| view | 뷰. 조회문만 저장하고 읽을 때마다 계산한다 | 가볍고 자주 안 읽는 모델. 기본값 |
| table | 테이블. 결과를 저장한다. 돌릴 때마다 전부 다시 만든다 | 여러 곳에서 자주 읽는 모델 |
| incremental | 테이블. 처음 한 번만 전부 만들고 뒤로는 새로 들어온 행만 처리해 더한다 | 행이 계속 쌓이는 큰 표 |
| ephemeral | 아무것도 안 남는다 | 다른 모델 안에 끼워 넣을 중간 계산 |
방식은 모델 파일 맨 위에 적습니다. 아래 한 줄이 이 모델을 뷰 대신 테이블로 만들게 합니다.
{{ config(materialized='table') }}
incremental 은 큰 표에서 비용을 줄이려고 씁니다. 로그처럼 하루에 수백만 행씩 쌓이는 표를 매번 처음부터 다시 만들면 시간과 요금이 계속 늘어납니다. 대신 모델 안에 「이미 있는 행보다 새로운 것만」 고르는 조건을 적어 두고, dbt 가 그 결과만 기존 테이블에 더합니다.
ephemeral 모델은 웨어하우스에 표를 만들지 않습니다. 이 모델을 ref 로 부른 모델 안에 CTE(Common Table Expression, 공통 테이블 식)로 끼워 들어갑니다.
CTE 는 WITH 절로 조회문 안에 이름을 붙여 둔 임시 결과입니다.
테스트
dbt 의 테스트는 만든 표의 값을 검사합니다. 앞의 셋째 문제, 곧 틀린 표가 조용히 넘어가는 일을 막으려는 장치입니다.
테스트는 「잘못된 행을 찾는 SELECT 문」입니다. 조회 결과가 한 행도 없으면 통과하고, 한 행이라도 나오면 실패합니다.
예를 들어 order_id 가 겹치지 않는지 보는 테스트는 대략 아래 조회문이 됩니다.
select order_id
from dev.stg_orders
group by order_id
having count(*) > 1 -- 0행이면 통과
자주 쓰는 검사는 dbt 에 이미 만들어져 있어서 YAML 에 이름만 적으면 됩니다. 모델의 열마다 붙입니다.
models:
- name: stg_orders
columns:
- name: order_id
tests:
- unique
- not_null
- name: status
tests:
- accepted_values:
values: ['paid', 'cancelled']
이 설정은 order_id 가 겹치지 않고 비어 있지 않은지, status 가 둘 중 하나의 값만 갖는지를 봅니다.
기본으로 들어 있는 검사는 넷입니다.
| 이름 | 무엇을 보나 |
|---|---|
| unique | 그 열에 같은 값이 두 번 나오지 않는다 |
| not_null | 그 열이 비어 있는 행이 없다 |
| accepted_values | 그 열이 정해 둔 값 목록 밖의 값을 갖지 않는다 |
| relationships | 그 열의 값이 다른 모델의 열에 반드시 있다 |
relationships 는 외래 키와 같은 검사입니다. Snowflake·BigQuery 같은 웨어하우스는 외래 키 제약을 적어 둘 수는 있습니다. 하지만 강제하지는 않습니다. 그래서 이 검사를 dbt 테스트로 돌립니다. 이 넷으로 부족하면 실패할 행을 찾는 SELECT 문을 직접 파일로 써서 테스트로 둡니다.
명령과 한 번의 실행
dbt 는 명령줄에서 부르는 도구입니다. 자주 쓰는 명령은 아래와 같습니다.
| 명령 | 하는 일 |
|---|---|
dbt run |
모델을 DAG 순서대로 만든다 |
dbt test |
테스트를 돌린다 |
dbt build |
모델을 만들고 바로 그 모델의 테스트까지 순서대로 돌린다 |
dbt compile |
웨어하우스에 보내지 않고 Jinja 만 풀어 SQL 파일로 남긴다 |
dbt seed |
프로젝트에 넣어 둔 작은 CSV(Comma-Separated Values, 쉼표로 구분한 값) 파일을 표로 올린다 |
dbt docs generate |
모델과 열의 설명, DAG 를 담은 문서 웹 페이지를 만든다 |
dbt run 한 번이 도는 과정은 이렇습니다. dbt 는 먼저 모델 파일을 다 읽어 DAG 를 만들고 Jinja 를 풀어 SQL 로 바꿉니다.
그다음 DAG 순서를 따라 모델마다 SQL 을 웨어하우스에 보내고 결과를 받습니다.
sequenceDiagram
participant 개발자
participant dbt
participant 웨어하우스
개발자->>dbt: dbt run
dbt->>dbt: 모델 파일을 읽어 DAG 를 만든다
dbt->>dbt: Jinja 를 풀어 SQL 로 컴파일한다
loop DAG 순서대로 모델마다
dbt->>웨어하우스: CREATE VIEW/TABLE ... AS SELECT
웨어하우스-->>dbt: 성공 또는 실패
end
Note over dbt,웨어하우스: 실패한 모델에 기대는 모델은 건너뛴다
dbt-->>개발자: 모델별 결과
한 모델이 실패해도 dbt 는 멈추지 않습니다. 그 모델을 ref 로 부르는 아래쪽 모델만 건너뜁니다. 상관없는 모델은 계속 만듭니다.
프로젝트와 연결 설정
dbt 프로젝트는 폴더 하나입니다. 맨 위에 dbt_project.yml 이라는 설정 파일이 있습니다. models 폴더 아래에는 모델 파일과 YAML 파일이 들어갑니다.
전부 글자 파일이라 Git 으로 버전을 관리하고 코드 리뷰를 거칠 수 있습니다.
웨어하우스에 붙는 주소와 계정은 프로젝트 밖의 profiles.yml 에 따로 둡니다. 비밀번호가 Git 저장소에 들어가지 않게 하려는 것입니다.
이 파일에는 개발용과 운영용 같은 실행 환경을 여럿 적어 둡니다. 명령을 낼 때 어느 쪽에 돌릴지 고릅니다.
웨어하우스마다 SQL 문법과 접속 방식이 조금씩 다릅니다. dbt 는 이 차이를 어댑터라는 플러그인에 맡깁니다. PostgreSQL 에는 PostgreSQL 어댑터를, Snowflake 에는 Snowflake 어댑터를 설치해서 붙입니다.
dbt Core 와 dbt Cloud
dbt 는 두 형태로 쓸 수 있습니다. dbt Core 는 Python 으로 짠 오픈 소스 CLI(Command-Line Interface, 명령줄 인터페이스) 도구입니다. Python 패키지 관리자인 pip 으로 설치합니다. 내 컴퓨터나 서버에서 돌립니다.
다른 하나는 dbt Cloud 입니다. dbt 를 만드는 회사 dbt Labs 가 운영하는 유료 서비스입니다. 웹 편집기와 작업 예약 기능을 얹어 줍니다.
dbt Core 에는 정해진 시각에 스스로 도는 기능이 없습니다. 매일 새벽에 모델을 다시 만들려면 cron 이나 Apache Airflow 같은 도구가 dbt build 를 불러 줘야 합니다.
이렇게 여러 작업의 순서와 시각을 관리하는 일을 워크플로우 오케스트레이션이라고 부릅니다.
문서와 리니지
ref 와 source 로 모은 DAG 는 실행 순서 말고도 쓰임이 있습니다. 어느 표가 어느 표에서 나왔는지를 그대로 보여 줍니다.
표가 어디서 와서 어디로 흘러가는지의 기록을 데이터 리니지라고 부릅니다.
dbt docs generate 가 만드는 문서 페이지는 이 DAG 를 그림으로 보여 줍니다. YAML 에 적어 둔 모델과 열의 설명도 같이 실립니다.
원본 표의 열 하나를 바꾸려 할 때 그 열에 기대는 마트가 무엇인지 여기서 찾습니다.
맞는 경우와 안 맞는 경우
dbt 는 두 조건이 맞을 때 씁니다. 데이터가 이미 웨어하우스에 들어와 있어야 합니다. 변환을 SQL 로 적을 수 있어야 합니다. 분석용 표가 수십 개 넘게 서로 얽힌 팀에서 특히 쓸모가 큽니다. 여러 사람이 그 표들을 고친다면 더 그렇습니다.
데이터를 원천에서 꺼내 오는 일에는 쓸 수 없습니다. 그 일은 Airbyte 같은 적재 도구가 따로 맡습니다. 이벤트가 들어오는 즉시 결과를 내야 하는 스트림 처리에도 맞지 않습니다. dbt 는 명령을 받을 때마다 한 번씩 도는 배치 처리 도구입니다.
대가도 있습니다. 모든 계산이 웨어하우스에서 돌아 그 요금이 곧 dbt 의 비용이 됩니다. table 방식 모델이 많으면 돌릴 때마다 전부 다시 계산하므로 요금이 빠르게 늡니다.
SQL 사이에 Jinja 가 섞이면 파일만 보고는 최종 SQL 이 바로 안 보입니다. 그래서 헷갈릴 때는 dbt compile 로 풀린 SQL 을 열어 봅니다.
관련 항목
dbt 가 맡는 데이터 처리 단계
ELT · ETL · 변환 · 추출 · 적재 · 데이터 파이프라인 · 배치 처리
dbt 가 속하는 분야
데이터 엔지니어링 · 애널리틱스 엔지니어 · 데이터 품질 · 데이터 모델링
dbt 가 조회문을 보내는 웨어하우스
데이터 웨어하우스 · Snowflake · BigQuery · Amazon Redshift · PostgreSQL · Databricks · DuckDB
dbt 프로젝트를 이루는 구성 요소
모델 (dbt) · source (dbt) · ref (dbt) · 테스트 (dbt) · 머티리얼라이제이션 · 스냅샷 (dbt) · 시드 (dbt) · 매크로 (dbt) · 어댑터 (dbt)
dbt 가 만드는 표의 종류
뷰 · 테이블 · 증분 모델 · CTE · 데이터 마트 · 스테이징 테이블 · 천천히 변하는 차원
dbt 가 기대는 언어와 형식
SQL · Jinja · 템플릿 엔진 · YAML · CSV · Python
dbt 테스트가 지키는 성질
외래 키 · 기본 키 · 참조 무결성 · NOT NULL 제약 · 유일성 제약 · 멱등성
dbt 를 부르고 순서를 정하는 도구
DAG · 워크플로우 오케스트레이션 · Apache Airflow · cron · Dagster · CI/CD
dbt 둘레에서 데이터를 옮기고 검사하는 도구
Airbyte · Fivetran · Great Expectations · Apache Spark · 데이터 리니지 · OpenLineage
dbt 의 배포 형태
다른 이름: data build tool · dbt Core · 디비티