개요
작년 8월쯤 DBT라는 Tool의 존재를 알게 되어 한 번 도입해볼까 했는데 DBT를 혼자 PoC 해보고 도입하기에는 무리가 있고 시기상조라고 파악되어 문서로 정리만 해놓고 Drop을 했었습니다. 당근에서도 DBT와 Airflow를 도입했다는 포스팅이 올라와서 순간 생각이 나서 제가 파악했었던 것을 정리하기 위해 포스트합니다.
DBT 도입 준비 배경
당시 회사에서는 Airflow 를 기반으로 하는 ELT 데이터 파이프라인을 구축하고 BigQuery 라는 플랫폼 내에서 SQL을 통해 데이터를 처리하는 구조를 가지고 있었는데 Raw 데이터를 거의 대부분 그대로 BigQuery로 올린 후 거기서 쿼리로 조작하는 경우가 많았기 때문에 ad-hoc한 쿼리도 많이 생기고 BigQuery 자체에 쿼리를 저장하는 경우도 많았기 때문에 나중에 재사용할 때 다시 작성하는 경우도 있고 쿼리를 수정해도 버전 관리 문제로 쿼리의 히스토리도 파악하기가 어려워 지는 경우가 발생했습니다. 또한, 주로 성능이나 안정성을 고려해서 SQL을 작성하다보니 가독성이 떨어지고 유지보수 또한 어려워지는 상황이 생깁니다. 그래서 이러한 쿼리들을 통합하고 정형화해서 개발자들이 Git을 통해 코드를 관리하고 유지보수 하듯이 SQL에도 도입할 수 있지 않을까해서 시작하게 되었습니다.
DBT란?
DBT는 ELT(Extract, Load, Transform) 중에서 T(Transform)을 위해 제작된 도구로 외부 데이터 소스로부터 데이터를 추출하거나 적재하는 기능을 하는 게 아닌 이미 적재되어 있는 데이터를 조회하고 수정하는 데에 최적화된 도구입니다.
DBT가 제안하는 해당 스택은 [Extract Source -> Extract & Loader -> Warehouse -> Consumer] 크게 4가지 영역으로 나누고 Warehouse 에서 일어나는 모든 Transform을 DBT가 담당하는 것으로 표현합니다.
ETL/ELT 프로세스의 T에 초점을 맞춘 거버넌스 도구라고 말할 수 있고 이를 통해 SQL에서 모든 데이터 변환을 중앙 집중화하고 구축하여 재사용 가능한 모듈(Model)로 구성할 수 있게 됩니다.
주요 기능
첫 번째로 코드 재사용이 가능한 부분인데 DBT는 데이터 모델이라는 것을 통해 결과물을 정의하면서 여러 패키지들을 지원합니다.
두 번째로 품질 검사에 대한 강조를 통해 데이터 품질을 보장하고 변환 오류를 방지하기 위해 자동화된 테스트의 사용을 권장합니다. 예시로 모델링하여 만든 테이블이 특정 테이블과 같은 로우 수를 갖는 지 비교하는 테스트를 다음과 같이 정의합니다.
version: 2
models:
- name: model_name
tests:
- dbt_utils.equal_rowcount:
compare_model: ref('other_table_name')
세 번째로 버전 제어 및 협업이 가능합니다. Git, Bitbucket 등과 같은 VCS(버전 제어 시스템)과 함께 작동하도록 설계되어 변경 사항을 쉽게 추척하고 개발 파이프라인에서 협업할 수 있습니다.
네 번째로 확장성입니다. BigQuery, Snowflake, Redshift 등과 같은 최신 데이터 웨어하우스와 함께 작동하도록 설계되어 대용량 데이터 처리를 쉽게 확장할 수 있습니다.
DBT 도입 시 장단점
장점
- 다양한 데이터 Unit Testing 으로 기존의 파이프라인을 견고하게 할 수 있습니다.
- 직관적인 Docs UI로 데이터를 찾기 위한 커뮤니케이션 비용 절감을 할 수 있습니다.
- 길어지는 SQL (500줄이 넘고...)을 재사용가능한 여러 개의 모듈로 쪼갤 수 있습니다.
- 코드 및 개발 사이클로 SQL을 관리하면서 SQL 리뷰 및 스타일 컨벤션 등 체계적인 쿼리 및 테이블 관리가 용이해집니다.
- 활용할 수 있는 다양한 macro와 오픈소스 툴들이 존재합니다.
- 따로 클라우드 및 온프레미스 리소스를 사용하지 않고, 기존의 DW 리소스를 효율적으로 활용할 수 있습니다.
- SQL 기반 데이터 오퍼레이션에 대한 오버헤드를 줄여주므로, 테이블 모델링과 아키텍처적인 고민에 집중할 수 있습니다.
단점
- YAML에 대해 체계적인 관리가 필요해집니다. 이로 인해 메뉴얼한 작업들이 생겨날 수 있습니다.
- SQL + Template 문법 + YAML 에 대한 교육이 필요해 러닝 커브가 존재합니다.
- Transform 만을 위한 도구라, Extract나 Load를 위해서는 다른 도구와 같이 사용해야 합니다.
- CLI만 사용하는 경우, 어떤 쿼리가 나가는 지 모니터링하기 쉽지가 않습니다. (Cloud 버전으로 유료 버전이 존재함.)
CLI vs Cloud
DBT는 오픈소스의 CLI 모드와 관리형 솔루션인 Cloud 모드가 있습니다.
CLI
Python 패키지로 DBT를 설치해서 사용하는 방법입니다. YAML을 사용해서 테이블의 메타 데이터 및 유닛 테스트 관리, Jinja Template을 통해서 다양한 매크로 및 UDF(User Defined Function)을 사용할 수 있고, 테이블 간의 Dependency를 계산해줍니다.
Cloud
CLI에서 제공하는 기능 외에도 SQL 통합 개발 환경(IDE)를 제공하고 웹 IDE를 통해서 분석가 및 SQL 사용자가 소프트웨어 개발자처럼 자신의 테이블에 대해 Unit Testing, 작업 관점에서 PR과 기본적인 Git Branch 작업 환경을 제어할 수 있게 해줌으로써 SQL 개발 프로세스를 전반적으로 유지보수 하기 쉽게 해준다고 합니다. 또한, CronJob 을 만들 수 있게 해준다고 합니다.
DBT 설치
homebrew, pip 등을 이용하여 설치할 수 있습니다.
pip install dbt-core==1.2.0
##
# dbt-postgres
##
RUN python -m pip install dbt-postgres==1.2.0
##
# dbt-bigquery
##
RUN python -m pip install dbt-bigquery==1.2.0
##
# dbt-databrick
##
RUN python -m pip install dbt-databrick==1.2.0
##
# dbt-snowflake
##
RUN python -m pip install dbt-snowflake==1.2.0
##
# dbt-redshift
##
RUN python -m pip install dbt-redshift==1.2.0
DBT 맛보기
DBT를 설치하고 프로젝트를 생성하면 다음과 같은 프로젝트 구조를 볼 수 있습니다.
├── README.md
├── analysis
├── data
├── dbt_project.yml
├── macros
├── models
│ └── example
│ ├── my_first_dbt_model.sql
│ ├── my_second_dbt_model.sql
│ └── schema.yml
├── snapshots
└── tests
- data
- CSV 파일 같은 데이터를 가져오기 위한 정적 파일들이 들어갑니다. dbt seed 명령을 통해서 INSERT 쿼리를 날려서 데이터 웨어하우스에 데이터를 적재할 수 있습니다.
- analysis
- 테이블을 만들기 위한 SQL이 아닌, 분석만을 위한 SQL 파일이 들어갑니다. 실제로 dbt run 명령을 통해서 쿼리가 실행되지는 않고, compile 테스트 (dbt compile)만을 위해서 사용됩니다.
- dbt_project.yml
- dbt 프로젝트를 위한 메타 정보가 들어갑니다.
- models
- dbt로 만드는 테이블들이 모여있는 곳 입니다. dbt의 메인 작업 디렉토리.
- macros
- 유저가 정의한 Jinja Template Macro가 존재하는 디렉토리입니다.
{% macro cents_to_dollars(column_name, precision=2) %}
({{ column_name }} / 100)::numeric(16, {{ precision }})
{% endmacro %}
위와 같은 매크로가 정의되어 있을 때, models 에서 다음과 같이 사용할 수 있습니다.
select
id as payment_id,
{{ cents_to_dollars('amount') }} as amount_usd,
...
from app_data.payments
- snapshot
- source 테이블들의 스냅샷에 대한 정보를 테이블로 만듭니다.
{% snapshot orders_snapshot %}
{{
config(
target_database='analytics',
target_schema='snapshots',
unique_key='id',
strategy='timestamp',
updated_at='updated_at',
)
}}
select * from {{ source('jaffle_shop', 'orders') }}
{% endsnapshot %}
dbt snapshot 명령을 통해 위 SQL 파일은 orders_snapshot 모델을 만듭니다. 첫 번째로 dbt snapshot 명령을 통해 쿼리가 수행되면, 해당 시점의 데이터와 dbt_valid_from 및 dbt_valid_to 라는 컬럼이 붙습니다. 두 번째 부터는 새로운 Row 가 해당 테이블에 INSERT 됩니다.
- tests
- 모델 및 스냅샷에 대한 테스트의 정의를 만듭니다.
DBT 용어 정리
Model
단순히 말해서 Table 이다. models 라는 폴더에 저장된 SQL 문들은 SELECT 문으로 구성되어 있고 SQL문과 YAML 파일을 참조하여 compile을 거쳐서 실행 시킬 수 있는 SQL 문으로 바뀌고 dbt run 이라는 명령을 통해 실행하게 되면 CREATE OR REPLACE TABLE AS 나 CREATE OR RELACE VIEW AS 명령에 Wrapping 되게 됩니다. 또한, incremental 이라는 옵션을 통해 테이블에 추가하는 방식도 가능합니다.
with source as (
select * from {{ ref('raw_orders') }}
),
renamed as (
select
id as order_id,
user_id as customer_id,
order_date,
status
from source
)
select * from renamed
Source
모델을 만들 때 사용할 원천 데이터/테이블을 말합니다.
Schema
Model에서 정의한 SQL을 통해 만들어질 테이블에 대한 명세를 말합니다. 어떤 테스트를 돌릴 것인지에 대한 명세도 작성이 가능합니다.
version: 2
models:
- name: transactions
description: 주문별 상세 정보를 담은 테이블
columns:
- name: order_id
tests:
- unique
- not_null
description: This is a unique identifier for an order
- name: customer_id
description: Foreign key to the customers table
tests:
- not_null
- relationships:
to: ref('stg_customers')
field: customer_id
- name: customer_name
description: customers full name
tests:
- not_null
결론
Tool 자체의 등장 배경은 이해가 됩니다. 당장 자그마한 데이터 플랫폼부터 시작해도 보이는 문제들이기도 해서 공감이 많이 되는 부분입니다. 다만 도입하기 위해서는 SQL 뿐만 아니라 YAML 작성이나 Jinja Template 같은 부분들도 알고 있어야 한다는 점이 러닝 커브가 조금 높지 않나라고 생각 됩니다. 다만 이 도구를 도입하고 시스템적으로 잘 녹여내고 사용자들에 대한 교육이 잘 이루어진다면 요즘처럼 ELT가 많은 시대에 좋은 도구라고 생각이 됩니다. 현재 회사의 데이터 플랫폼을 봤을 때는 도입하기에 좀 어려운 구조를 가지고 있어서 내용 정리하고 PoC만 진행해 보았지만 나중에 기회가 된다면 도입해보고 싶네요.
Reference