LLM WikiAccess-protected knowledge portal
← 스터디 홈
5편 · 약 15분

배포와 CI/CD

dbt에서 배포가 어려운 이유

일반 애플리케이션 배포는 새 바이너리나 컨테이너를 올리고, 문제가 생기면 이전 버전으로 되돌리는 그림으로 설명할 수 있다. dbt 배포는 조금 다르다. 배포 결과가 코드만이 아니라 웨어하우스 안의 테이블, 뷰, 스냅샷, 테스트 결과, 문서, 메타데이터 아티팩트로 남는다. 잘못된 모델 하나가 배포되면 대시보드가 깨지는 데서 끝나지 않고, 그 모델을 참조하는 하위 마트와 리포트까지 연쇄적으로 영향을 받는다.

그래서 dbt의 CI/CD는 "PR이 열리면 SQL 문법을 검사한다" 수준으로는 부족하다. 좋은 파이프라인은 세 가지 질문에 답해야 한다.

  1. 이 변경이 컴파일되는가?
  2. 이 변경이 영향을 주는 모델과 테스트가 실제로 통과하는가?
  3. 통과한 코드가 운영 환경에 반영될 때 어떤 순서와 격리 수준으로 실행되는가?
feature branch Pull Request CI 환경 dbt deps dbt build --select state:modified+ 임시 PR schema merge Production deploy dbt build source freshness docs / artifacts
dbt 배포 파이프라인의 기본 흐름

핵심은 PR 검증과 운영 배포를 분리하는 것이다. PR에서는 변경 범위를 작게 잡아 빠르게 피드백하고, 운영에서는 합쳐진 main 브랜치를 기준으로 재현 가능한 명령을 실행한다.

환경 설계: dev, CI, staging, prod

dbt 환경은 보통 네 층으로 나눈다.

환경목적주의점
dev개인 개발과 로컬 검증사용자별 schema를 분리한다.
CIPR 검증PR별 임시 schema를 만들고, 운영과 충돌하지 않게 한다.
staging릴리스 후보 검증팀 규모가 크거나 배포 묶음이 필요할 때 둔다.
prod운영 데이터 생성최종 source of truth 환경으로 관리한다.

작은 팀이라면 dev → CI → prod만으로도 충분하다. 그러나 변경이 많고 데이터 소비자가 많다면 staging을 추가하는 편이 안전하다. 예를 들어 여러 PR을 하루에 한 번 묶어 배포하는 팀은 staging 브랜치에 먼저 머지한 뒤, 일정 시간 동안 대시보드와 주요 테스트를 확인하고 main으로 승격할 수 있다.

중요한 것은 이름보다 격리다. CI가 운영 schema에 직접 쓰면 안 된다. dbt Cloud의 CI 환경은 PR별 임시 schema를 자동으로 만들 수 있고, dbt Core를 GitHub Actions나 GitLab CI에서 돌리는 경우에도 schema: ci_pr_{{ env_var('PR_NUMBER') }} 같은 방식으로 PR별 schema를 분리해야 한다.

PR 검증: Slim CI

대형 dbt 프로젝트에서 모든 PR마다 dbt build 전체를 실행하면 피드백이 느리고 비용도 커진다. 이때 쓰는 패턴이 Slim CI다.

dbt build --select state:modified+ --defer --state path/to/prod-artifacts

각 옵션의 의미는 다음과 같다.

옵션의미
state:modified+이전 manifest와 비교해 변경된 노드와 그 하위 의존 노드를 선택한다.
--state비교 기준이 되는 과거 dbt 아티팩트 디렉터리를 지정한다. 보통 운영 manifest.json을 둔다.
--defer선택되지 않은 상위 모델은 CI schema에 새로 만들지 않고, 운영 manifest가 가리키는 relation을 참조할 수 있게 한다.

이 조합 덕분에 PR에서 바꾼 모델과 그 영향 범위만 빌드한다. 예를 들어 stg_orders를 고치면 stg_orders와 그 하위 int_orders, fct_orders 테스트는 돌지만, 전혀 관련 없는 마케팅 마트는 건드리지 않는다.

raw_orders stg_orders modified int_orders downstream fct_orders downstream stg_customers deferred to prod tests selected 파란색은 CI에서 실제 빌드·테스트하는 범위, 점선 회색은 운영 relation을 참조하는 범위
Slim CI의 선택 범위

manifest.json은 dbt 프로젝트의 리소스, 설정, 의존 관계를 담은 상태 파일이다. dbt 명령 대부분은 실행 후 target/manifest.json을 만든다. CI에서 Slim CI를 쓰려면 운영 배포가 성공할 때마다 이 파일을 저장해 두어야 한다. dbt Cloud는 환경과 job의 아티팩트를 관리해 주고, dbt Core 기반 CI에서는 S3, GCS, Azure Blob, artifact branch 같은 외부 저장소에 업로드하는 방식이 흔하다.

GitHub Actions로 dbt Core 배포하기

dbt Cloud를 쓰면 UI에서 CI job과 deploy job을 만들 수 있지만, dbt Core만으로도 GitHub Actions 같은 CI 도구에서 충분히 운영할 수 있다. 기본 구조는 다음과 같다.

name: dbt CI

on:
  pull_request:
    branches: [ main ]

jobs:
  dbt-ci:
    runs-on: ubuntu-latest
    env:
      DBT_PROFILES_DIR: .github/dbt
      DBT_TARGET: ci
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dbt
        run: |
          python -m pip install --upgrade pip
          pip install dbt-core dbt-postgres

      - name: Write profiles.yml
        run: |
          mkdir -p .github/dbt
          printf '%s' "${{ secrets.DBT_PROFILES_YML }}" > .github/dbt/profiles.yml

      - name: Download production artifacts
        run: |
          mkdir -p target-prod
          aws s3 cp s3://my-dbt-artifacts/prod/manifest.json target-prod/manifest.json

      - name: Validate changed dbt graph
        run: |
          dbt deps
          dbt build --target ci --select state:modified+ --defer --state target-prod

이 예시는 개념을 보여주기 위한 것이다. 실제 운영에서는 어댑터(dbt-snowflake, dbt-bigquery, dbt-databricks 등), 클라우드 인증 방식, 네트워크 접근 제어를 팀 환경에 맞게 바꿔야 한다.

운영 배포 workflow는 PR이 아니라 main push나 스케줄을 기준으로 실행한다.

name: dbt Deploy

on:
  push:
    branches: [ main ]
  schedule:
    - cron: '0 18 * * *' # UTC 기준. KST 03:00

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      DBT_PROFILES_DIR: .github/dbt
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install dbt
        run: pip install dbt-core dbt-postgres
      - name: Write profiles.yml
        run: |
          mkdir -p .github/dbt
          printf '%s' "${{ secrets.DBT_PROFILES_YML }}" > .github/dbt/profiles.yml
      - name: Build production assets
        run: |
          dbt deps
          dbt source freshness --target prod
          dbt build --target prod
          dbt docs generate --target prod
      - name: Upload artifacts
        run: |
          aws s3 cp target/manifest.json s3://my-dbt-artifacts/prod/manifest.json
          aws s3 cp target/run_results.json s3://my-dbt-artifacts/prod/run_results.json

주의할 점은 GitHub Actions의 cron이 UTC 기준이라는 것이다. KST 새벽 3시에 돌리고 싶다면 전날 18:00 UTC로 설정해야 한다.

배포 명령은 dbt run보다 dbt build가 기본값

운영 배포에서 dbt run 뒤에 dbt test를 따로 실행하는 방식도 가능하지만, 요즘 기본값은 dbt build다. dbt build는 모델, 테스트, 스냅샷, seed를 DAG 순서대로 처리하고, 선행 노드가 실패하면 하위 노드를 스킵한다. 운영 파이프라인에서는 이 동작이 중요하다. 잘못된 upstream 모델을 만든 뒤 downstream 모델까지 계속 실행하는 일을 줄여 주기 때문이다.

운영 job의 흔한 명령 순서는 다음과 같다.

dbt deps
dbt source freshness
dbt build
dbt docs generate

source freshness는 원천 데이터가 기대한 시간 안에 들어왔는지 확인한다. 예를 들어 주문 원천이 2시간 넘게 갱신되지 않았는데 매출 마트를 정상으로 빌드하면, 사용자는 "데이터가 정상적으로 최신"이라고 오해할 수 있다. freshness 실패를 배포 실패로 볼지, 경고로만 볼지는 데이터 제품의 SLA에 맞춰 정해야 한다.

secrets와 profiles.yml 관리

CI/CD에서 가장 자주 나는 사고는 profiles.yml이나 키 파일이 저장소에 커밋되는 것이다. dbt 프로젝트 저장소에는 연결 정보의 구조만 두고, 실제 비밀번호와 키는 CI secret store에 둔다.

좋은 패턴은 다음과 같다.

  • profiles.yml 자체를 secret으로 저장하거나, template 파일에서 env_var()로 값을 읽게 한다.
  • 운영 credential과 CI credential을 분리한다.
  • CI credential은 임시 schema 생성과 테스트에 필요한 최소 권한만 준다.
  • production credential은 승인된 deploy job에서만 쓰게 한다.
  • target/, logs/, 로컬 key 파일은 .gitignore에 넣는다.

예를 들어 profiles.yml은 다음처럼 환경 변수를 읽게 만들 수 있다.

jaffle_shop:
  target: "{{ env_var('DBT_TARGET', 'dev') }}"
  outputs:
    ci:
      type: postgres
      host: "{{ env_var('DBT_HOST') }}"
      user: "{{ env_var('DBT_CI_USER') }}"
      password: "{{ env_var('DBT_CI_PASSWORD') }}"
      dbname: analytics
      schema: "ci_pr_{{ env_var('PR_NUMBER') }}"
      threads: 4
    prod:
      type: postgres
      host: "{{ env_var('DBT_HOST') }}"
      user: "{{ env_var('DBT_PROD_USER') }}"
      password: "{{ env_var('DBT_PROD_PASSWORD') }}"
      dbname: analytics
      schema: marts
      threads: 8

이렇게 하면 같은 dbt 프로젝트라도 CI와 prod가 서로 다른 권한과 schema를 사용한다.

문서와 아티팩트도 배포 산출물이다

dbt 배포의 산출물은 테이블만이 아니다. manifest.json, run_results.json, catalog.json, dbt docs HTML도 운영에 필요한 정보다.

산출물용도
manifest.jsonDAG, 모델 설정, 테스트, 매크로 등 프로젝트 상태. Slim CI와 문서 생성의 핵심 입력.
run_results.json각 노드의 실행 결과와 시간. 실패 분석과 운영 리포팅에 유용.
catalog.json웨어하우스 컬럼, 타입, 통계 등 catalog 정보. docs site에 사용.
docs site데이터 소비자와 분석가가 모델 설명, lineage, 테스트 정보를 확인하는 UI.

운영 job이 성공할 때마다 아티팩트를 저장하면 다음 PR의 Slim CI 기준점이 안정된다. 반대로 운영 아티팩트가 오래되면 CI가 필요 이상으로 많은 모델을 선택하거나, 실제 운영과 다른 기준으로 검증할 수 있다.

배포 전략: 즉시 배포와 릴리스 트레인

dbt 배포 전략은 팀의 변경량과 데이터 소비자 수에 따라 달라진다.

즉시 배포

PR이 main에 merge되면 바로 production job이 실행된다. 작은 팀이나 모델 수가 적은 프로젝트에 적합하다.

장점은 빠르다는 것이다. 단점은 PR이 자주 merge될수록 운영 job이 자주 돌고, 실패가 발생했을 때 어떤 변경이 원인인지 빠르게 추적해야 한다는 점이다.

릴리스 트레인

여러 변경을 staging이나 release branch에 모은 뒤 정해진 시간에 production으로 승격한다. 데이터 소비자가 많고, 주요 대시보드 검증 시간이 필요한 조직에 적합하다.

장점은 운영 변경 창을 통제할 수 있다는 것이다. 단점은 릴리스 관리자가 필요하고, staging manifest가 자주 바뀌면 CI의 state comparison 기준이 흔들릴 수 있다는 점이다.

실패 대응과 롤백

dbt에서 롤백은 애플리케이션 롤백보다 까다롭다. 이전 SQL로 되돌려도 이미 만들어진 테이블 데이터가 자동으로 과거 상태가 되지는 않는다. 따라서 롤백 전략은 모델 유형별로 나눠야 한다.

모델 유형실패 대응
view이전 코드로 되돌려 재배포하면 비교적 쉽게 복구된다.
table이전 코드로 재생성해야 한다. 큰 테이블은 시간이 오래 걸릴 수 있다.
incremental잘못 들어간 행을 삭제하거나 --full-refresh가 필요할 수 있다.
snapshot과거 이력 자체가 오염될 수 있어 수동 보정 절차가 필요하다.

운영적으로는 다음 세 가지가 중요하다.

  1. 배포마다 commit SHA와 dbt run ID를 남긴다.
  2. 실패한 노드와 하위 영향 범위를 즉시 확인할 수 있게 run_results.json을 보관한다.
  3. 대용량 incremental 모델은 무작정 --full-refresh하지 말고, 재처리 파티션과 비용을 먼저 계산한다.

실무 체크리스트

영역체크할 것
브랜치 정책main 직접 push 금지, PR 필수, CI 통과 후 merge
CI 범위dbt build --select state:modified+로 변경 영향 범위 검증
상태 기준운영 manifest.json을 안정적으로 저장하고 CI에서 사용
환경 격리PR별 schema, 운영 credential 분리
운영 명령dbt deps, source freshness, dbt build, docs generate
아티팩트manifest.json, run_results.json, catalog.json 저장
알림source freshness 실패, build 실패, 테스트 실패를 채널에 알림
비용 관리전체 build와 Slim CI 실행 시간을 따로 관측
롤백view/table/incremental/snapshot별 복구 절차 문서화

한 줄 정리

dbt CI/CD의 핵심은 PR에서는 state:modified+--defer로 변경 영향 범위만 빠르게 검증하고, 운영에서는 격리된 credential로 dbt build를 재현 가능하게 실행하며, 다음 CI의 기준이 될 아티팩트를 안정적으로 남기는 것이다.

References

  • https://docs.getdbt.com/docs/deploy/ci-jobs
  • https://docs.getdbt.com/guides/set-up-ci
  • https://docs.getdbt.com/docs/deploy/deploy-jobs
  • https://docs.getdbt.com/docs/deploy/deploy-environments
  • https://docs.getdbt.com/reference/node-selection/state-selection
  • https://docs.getdbt.com/reference/node-selection/defer
  • https://docs.getdbt.com/reference/artifacts/manifest-json
  • https://docs.getdbt.com/docs/build/sources