SQLMesh 사용법 : dbt의 경쟁자가 될 수 있을까?
in Data Engineering on SQLMesh
- 이 글은 SQLMesh 사용법에 대해 작성한 글입니다
- 예상 독자
- SQLMesh가 궁금하신 분
- SQLMesh가 dbt 대비 어떤 것이 다른지 궁금하신 분
- 이 글을 보시기 전에 dbt, DuckDB에 대해 알고 계시면 좋을 수 있는데, 과거에 작성한 글을 추천
- 키워드 : SQLMesh 사용법, SQLMesh vs dbt
SQLMesh란?
- SQLMesh GitHub
- Tobiko의 오픈소스
- SQLMesh는 Transformation, Testing, Collaboration을 위한 DataOps 솔루션
- 데이터 Transformation 영역에서 dbt가 제일 유명한데, dbt에서 개선하면 좋은 점들을 개선해서 나오는 도구
- SQLMesh 공식 문서에 보면 dbt와 다른 점에서도 상세히 공유하고 있음
- Terraform에서 영감을 받은 부분이 있다고 함. 그래서 plan 명령어가 있음
- 2025년 3월 15일 기준 v0.165.0
SQLMesh의 장점
- SQLMesh의 Concept은 dbt와 유사하며, dbt와 차이점으로 나오는 장점들을 정리
1) 간단한 SQL로 모델 생성 가능
- 데이터 분석가 직무에 계신 분들이 처음 dbt를 사용하면 Jinja, YAML에 어려움을 겪는데, SQLMesh는 간단한 SQL로 정의할 수 있음
2) 증분 로직 처리
- dbt에선 증분 로직을
is_incremental
등을 사용해서 만들어야 하는데, SQLMesh는 증분 로직을 쉽게 만들어줌 - dbt의 증분 로직
- 처음에 dbt를 학습할 때 제일 어려운 부분
- dbt는 state를 추적하지 않아서 이렇게 구현해야 함
- 날짜 경계를 찾기 위해 수동으로 매크로를 지정하는 작업에서 오류가 나기 쉬움
-- dbt incremental SELECT * FROM {{ ref(raw.events) }} e JOIN {{ ref(raw.event_dims) }} d ON e.id = d.id -- must specify the is_incremental flag because this predicate will fail if the model has never run before {% if is_incremental() %} -- this filter dynamically scans the current model to find the date boundary AND d.ds >= (SELECT MAX(ds) FROM {{ this }}) {% endif %} {% if is_incremental() %} WHERE e.ds >= (SELECT MAX(ds) FROM {{ this }}) {% endif %}
- SQLMesh는 날짜 범위를 알아서 추적해 간단하게 쓸 수 있음
-- SQLMesh incremental
SELECT *
FROM raw.events e
JOIN raw.event_dims d
-- date ranges are handled automatically by SQLMesh
ON e.id = d.id AND d.ds BETWEEN @start_ds AND @end_ds
WHERE d.ds BETWEEN @start_ds AND @end_ds
3) Data Gap 감지
- dbt에서 증분 모델을 구현할 때는 MAX(date)로 최근 데이터를 확인하고, 그 이후부터 적재하도록 함
- 이렇게 할 경우 과거에 누락된 데이터를 놓치게 되며, 데이터 파이프라인 관점에서 유지보수를 더 많이 해야 함
SQLMesh는 모델이 실행된 날짜 간격을 저장해서 누락된 날짜를 정확히 알 수 있음
Expected dates: 2022-01-01, 2022-01-02, 2022-01-03 Missing past data: ?, 2022-01-02, 2022-01-03 Data gap: 2022-01-01, ?, 2022-01-03
4) 환경 격리
- 데이터 파이프라인도 Prod/Dev 환경을 격리해야 할 수 있음. 규모가 커질수록 환경의 필요성이 생기지만, 환경을 구축하는 것은 고민이 많이 됨
- 환경을 구축하기 위해선 실제 데이터를 Dev에 그대로 제공해야 하는데, 이는 데이터 저장 비용이 드는 일이라 고민이 됨(데이터 저장 비용이 2배가 되는 상황)
- dbt는 환경마다 별도의 테이블에 데이터를 물리적으로 복제함
- SQLMesh는 VIEW를 사용해서 데이터를 물리적으로 저장하지 않아 비용이 들지 않음
- 환경마다 테이블 이름에 접두사를 붙여서 환경을 구분함
5) 스키마 변경 자동화
- 스키마가 변경되면 자동으로 감지하고 필요한 마이그레이션을 생성함
- dbt는 개발자가 직접 스키마 변경을 관리해야 함
6) 오픈소스 UI 제공
- dbt는 Paid UI가 제공되며, 오픈소스 중에 찾으려면 lightdash를 사용해야 함
- SQLMesh는 오픈소스 UI가 존재해서 쉽게 사용할 수 있음(그러나 디자인은 약간 아쉬움)
SQLMesh 실습
작업 흐름
- 파이썬 가상 환경 / 라이브러리 설치
- 파이썬 가상환경 설정
- SQLMesh 설치
- 프로젝트 생성
- Prod 환경 설정
- Model 수정
- Dev에서 작업
- Prod로 통합
파이썬 가상 환경 / 라이브러리 설치
- 가상 환경 설정
python -m venv .venv
source .venv/bin/activate
- SQLMesh 설치
- Web UI도 사용할 예정이라, 같이 설치
pip3 install "sqlmesh[web]"
- Duckdb 설치 : Local에서 Duckdb를 사용할 예정
pip3 install duckdb
프로젝트 생성
- 폴더 생성(sqlmesh-example)
mkdir sqlmesh-example
cd sqlmesh-example
DuckDB 환경에서 init
sqlmesh init duckdb
- 다음과 같은 폴더, 파일들이 생성됨
├── audits
│ └── assert_positive_order_ids.sql
├── config.yaml
├── macros
│ └── __init__.py
├── models
│ ├── full_model.sql
│ ├── incremental_model.sql
│ └── seed_model.sql
├── seeds
│ └── seed_data.csv
└── tests
└── test_full_model.yaml
6 directories, 8 files
config.yaml
- 프로젝트 디렉토리에 생성됨
- 어떤 엔진을 사용할지, 게이트웨이 커넥션 등이 저장됨
gateways:
duckdb:
connection:
# For more information on configuring the connection to your execution engine, visit:
# https://sqlmesh.readthedocs.io/en/stable/reference/configuration/#connections
# https://sqlmesh.readthedocs.io/en/stable/integrations/engines/duckdb/#connection-options
type: duckdb
database: db.db
# concurrent_tasks: 1
# register_comments: True
# pre_ping: False
# pretty_sql: False
# catalogs:
# extensions:
# connector_config:
# token:
default_gateway: duckdb
model_defaults:
dialect: duckdb
start: 2025-03-14
생성된 폴더의 특징
- config.yaml : 프로젝트 설정 파일
- models : SQL/Python 모델 파일
- 현재 full_model.sql, incremental_model.sql, seed_model.sql이 저장됨
- seeds : CSV 같은 고정된 데이터셋
- audits : 모델의 출력을 검증할 때 사용
- 목적: 모델 출력의 데이터 품질을 검증
- 실행 시점: 모델 실행 후 매번 자동으로 실행. 계획(plan)을 적용할 때마다 SQLMesh는 자동으로 실행
- 기본 동작: 감사가 실패하면 sqlmesh plan을 중단해 잠재적으로 유효하지 않은 데이터가 다운스트림으로 전파되는 것을 방지
- tests : 모델의 로직의 정확성 검증
- 목적: 모델의 로직을 검증하고 예상된 출력과 일치하는지 확인
- 실행 시점: 개발 중, 또는 CI/CD 과정
- 작동 방식: 사전 정의된 입력으로 모델의 로직을 평가하고 출력을 테스트에서 제공된 예상 결과와 비교
- 소프트웨어 개발의 단위 테스트(unit testing)와 유사
- macros : 매크로 저장. SQLMesh 매크로와 Jinja 매크로로 나뉨
Model 생성
- Model Doc
- 위에 init하면서 생긴 Model 살펴보기
Full Model
- 최상단에 MODEL 명령어를 사용해 모델을 정의
- name은 프로젝트 이름에서 Unique해야 함
- kind : 모델의 종류를 의미
- Model Kind properties에서 확인할 수 있음
- VIEW, FULL, Incremental Model이 존재
- Incremental Model은 INCREMENTAL_BY_TIME_RANGE, INCREMENTAL_BY_UNIQUE_KEY, INCREMENTAL_BY_PARTITION로 나뉨
- cron : 모델이 데이터를 처리하거나 새로고침을 할 때 사용할 일정. 자체 스케쥴러를 쓸 수도 있고, Airflow를 사용할 수도 있음
- grain : 모델이 반환한 결과에서 행을 고유하게 식별할 수 있는 컬럼, 여러 컬럼의 조합도 가능함
- audits : 모델이 평가된 후 실행될 오딧(여기선 audits/assert_positive_order_ids.sql 파일을 실행한다는 의미)
- 그 후 모델의 쿼리
- 만약 컬럼의 데이터를 형변환하고 싶다면 다음과 같이 정의하면 됨
item_id::TEXT
- 만약 컬럼의 데이터를 형변환하고 싶다면 다음과 같이 정의하면 됨
MODEL (
name sqlmesh_example.full_model,
kind FULL,
cron '@daily',
grain item_id,
audits (assert_positive_order_ids),
);
SELECT
item_id,
COUNT(DISTINCT id) AS num_orders,
FROM
sqlmesh_example.incremental_model
GROUP BY item_id
- 비교를 위해 dbt 모델 파일도 추가. 모델 정의 자체는 비슷하다고 볼 수 있지만, 명시적으로 MODEL을 정의하는 점이 차이
-- models/staging/stg_orders.sql
{{ config(materialized='table') }}
SELECT
order_id,
customer_id,
order_date,
status,
amount
FROM raw_data.orders
WHERE status IS NOT NULL
Incremental Model
- INCREMENTAL_BY_TIME_RANGE을 사용했으며, 시간 컬럼을 event_date로 지정
- grain을 id, event_date로 설정함
- SELECT 쿼리 하단을 보면 WHERE에 event_date BETWEEN @start_date AND @end_date가 있는데, 이렇게 증분 처리가 끝남
MODEL (
name sqlmesh_example.incremental_model,
kind INCREMENTAL_BY_TIME_RANGE (
time_column event_date
),
start '2020-01-01',
cron '@daily',
grain (id, event_date)
);
SELECT
id,
item_id,
event_date,
FROM
sqlmesh_example.seed_model
WHERE
event_date BETWEEN @start_date AND @end_date
Seed Model
- Seed Model Doc
- Seed Model을 불러오고 싶은 경우 사용
- 컬럼의 이름과 타입을 지정할 수 있음
MODEL (
name sqlmesh_example.seed_model,
kind SEED (
path '../seeds/seed_data.csv'
),
columns (
id INTEGER,
item_id INTEGER,
event_date DATE
),
grain (id, event_date)
);
Audit
- Audit Document
- assert_positive_order_ids.sql
- 최상단에 AUDIT을 명시하고, 이름 지정
- FROM 절에 @this_model을 사용하는데, 이는 SQLMesh의 빌트인 매크로임. Runtime Variable에서 확인할 수 있음
AUDIT (
name assert_positive_order_ids,
);
SELECT *
FROM @this_model
WHERE
item_id < 0
- 파라미터를 주입할 수도 있음. 특정 값이 초과되지 않는 경우를 확인하고 싶은 Audit
AUDIT (
name does_not_exceed_threshold
);
SELECT * FROM @this_model
WHERE @column >= @threshold;
- 위 Audit을 실행하려면 MODEL에서 아래와 같이 정의해야 함
MODEL (
name sushi.items,
audits (
does_not_exceed_threshold(column := id, threshold := 1000),
does_not_exceed_threshold(column := price, threshold := 100)
)
);
SQLMesh Plan
- SQLMesh에선 환경에 Plan을 만들고 적용하는 것이 핵심
- 모델의 새 버전을 생성하고 저장함
- 모델을 평가하고 실행
- Plan의 Target 환경에 업데이트
sqlmesh plan
- Backfill Tables에 y를 입력하면 Backfill 작업을 수행함
- 폴더를 보면,
db.db
와 logs가 추가된 것을 확인할 수 있음
.
├── audits
│ └── assert_positive_order_ids.sql
├── config.yaml
├── db.db
├── logs
│ ├── sqlmesh_2025_03_15_14_04_40.log
│ └── sqlmesh_2025_03_15_14_31_00.log
├── macros
│ ├── __init__.py
│ └── __pycache__
│ └── __init__.cpython-310.pyc
├── models
│ ├── full_model.sql
│ ├── incremental_model.sql
│ └── seed_model.sql
├── seeds
│ └── seed_data.csv
└── tests
└── test_full_model.yaml
8 directories, 12 files
DuckDB에서 데이터 확인
- CLI에서 아래 명령어 실행
duckdb db.db
- DuckDB를 실행한 후,
.tables
를 실행하면 테이블을 확인할 수 있음- full_model, incremental_model, seed_model : 위 MODEL에서 생성한 결과. 사용자가 사용할 땐 이걸 사용
sqlmesh_example__full_model__2278521865
: 이 형태도 물리적 테이블이나, 버전 관리를 위해 사용하는 내부적인 테이블
D .tables
_auto_restatements
_environment_statements
_environments
_intervals
_plan_dags
_snapshots
_versions
full_model
incremental_model
seed_model
sqlmesh_example__full_model__2278521865
sqlmesh_example__incremental_model__1880815781
sqlmesh_example__seed_model__2185867172
- 아래 쿼리를 실행하면 데이터를 확인할 수 있음
- sqlmesh_example는 모델의 스키마로 위에 모델 생성할 때 사용함
SELECT * FROM sqlmesh_example.full_model;
모델 수정 및 Dev에서 작업하기
모델 수정
- 이제 Prod 환경을 채웠으니, SQL 모델 하나를 수정
models/incremental_model.sql
에'z' AS new_column
을 추가함
MODEL (
name sqlmesh_example.incremental_model,
kind INCREMENTAL_BY_TIME_RANGE (
time_column event_date
),
start '2020-01-01',
cron '@daily',
grain (id, event_date)
);
SELECT
id,
item_id,
'z' AS new_column, -- Added column
event_date,
FROM
sqlmesh_example.seed_model
WHERE
event_date BETWEEN @start_date AND @end_date
Dev 환경 생성 및 Backfill
dev 환경을 생성
sqlmesh plan dev
생성하면 변경된 모델이 incremental_model이라고 나오며, 변경된 부분이 나옴
- backfill을 해주면, dev 환경에 테이블이 생성됨
데이터 확인
- 위에서 했던 것처럼
duckdb db.db
로 DuckDB에 접근한 후,.tables
로 테이블 확인
D .tables
_auto_restatements
_environment_statements
_environments
_intervals
_plan_dags
_snapshots
_versions
full_model
full_model
incremental_model
incremental_model
seed_model
sqlmesh_example__full_model__2278521865
sqlmesh_example__full_model__2278521865__dev # 새로 추가됨
sqlmesh_example__incremental_model__1880815781
sqlmesh_example__incremental_model__772980299 # 새로 추가됨
sqlmesh_example__seed_model__2185867172
- full_model은 변경된 것이 없어서 기존 모델에 __dev 추가(새 환경)
- 반면 incremental_model은 변경된 것이 있어서 해시가 변경됨
- 새롭게 추가된 incremental_model 데이터 확인
select * from sqlmesh_example__dev.incremental_model;
Dev에서 작업한 내용 Prod에 적용
- sqlmesh plan 명령어를 사용해서 Dev -> Prod로 적용
- DuckDB에 접속하지 않고, sqlmesh 명령어로 데이터를 가지고 올 수도 있음
sqlmesh fetchdf "select * from sqlmesh_example.incremental_model"
SQLMesh Web UI
- 아래 명령어를 통해 Web UI를 실행
sqlmesh ui
- 위에서 작업한 incremental_model.sql을 클릭하니 리니지를 보여줌
- 데이터를 쿼리해서 확인할 수도 있음
- Models 폴더를 클릭하면 실행된 모델과 모델의 메타데이터, 리니지를 볼 수 있음
SQLMesh 사용 후기
- SQLMesh를 사용해보면 dbt보다 러닝커브가 낮음
- 그러나 지금 SQLMesh롤 바로 도입해야 할까? 하면 고민되는 부분이 있음
- 생태계가 아직 dbt에 비해 작고, 1점대도 아니라 계속 변경될 것으로 예상
- Airflow와 Prefect의 관계를 보는 것 같기도 함
- 그럼에도 불구하고 T 영역에서 거의 독점했던 dbt의 경쟁자가 있다는 점에서 SQLMesh를 응원하고 싶음. 사용성은 괜찮았음
- dbt가 SDF labs를 인수한 후, 여러 통합 작업을 진행할 것으로 예상되는데 그 과정에서 SQLMesh가 제시하는 SQLMesh의 장점을 무력화할 수 있을 것 같음
- 레딧에서 논의되는 내용을 보면 도움이 됨
레퍼런스 및 읽으면 좋은 자료
- SQL 홈페이지
- SQLMesh Document
- Sung Won Chung님의 링크드인 : Tobiko의 Solution Architect로 근무하고 계신데 튜토리얼 내용을 링크드인 글로 공유해주심
- Tobiko 블로그
- dbt와 SQLMesh의 기능 비교 : 각 라이브러리가 제공하는 범위를 비교해줌
- FAQ 문서도 보는 것을 추천함. dbt와 SQLMesh의 용어가 살짝 다름
- dbt materialization : SQLMesh의 model kinds에 정의
- dbt seeds : SQLMesh model kind의 seed
- dbt test : SQLMesh audits
- dbt build : sqlmesh run
- SQLMesh + GitHub Action 통합
- SQLMesh를 사용한 데이터 파이프라인
- 글 작성하는데 걸린 시간 : 3시간 30분
- 하고자 하는 이야기, 개요 정리 : 13분
- 초안 글 작성 : 3시간 7분
- 클로드와 셀프 글 피드백 : 10분
카일스쿨 유튜브 채널을 만들었습니다. 데이터 분석, 커리어에 대한 내용을 공유드릴 예정입니다.
PM을 위한 데이터 리터러시 강의를 만들었습니다. 문제 정의, 지표, 실험 설계, 문화 만들기, 로그 설계, 회고 등을 담은 강의입니다
이 글이 도움이 되셨거나 의견이 있으시면 댓글 남겨주셔요.