dbt 7 — Triển khai, môi trường & CI/CD

13 thg 7, 2026 2 lượt xem
#deployment
#ci-cd
#data-engineering
#airflow
#dbt
#slim-ci

Từ "chạy được trên máy tôi" đến production

Suốt series này ta đã đi từ tổng quan dbt, qua models & materializations, sources & snapshots, tests & docs, rồi tới incremental & performance. Tất cả những gì đã học đều xoay quanh một hành động duy nhất: gõ dbt run trên máy mình và nhìn model được dựng. Nhưng một project dbt chỉ thực sự tạo ra giá trị khi nó chạy tự động, đúng giờ, trong môi trường production, và khi mọi thay đổi đều được kiểm chứng trước khi chạm vào dữ liệu thật.

Bài cuối này khép lại series bằng ba câu hỏi vận hành: chạy dbt ở đâu (môi trường dev vs prod), chạy bằng cách nào (scheduler nào), và làm sao để mỗi pull request an toàn mà không tốn kém (Slim CI). Đây cũng là nơi dbt gặp lại Airflow — công cụ điều phối ta đã mổ xẻ ở series riêng.

Quy ước: sandbox của chúng ta là PostgreSQL và dbt không thực sự chạy ở đây, nên mọi đoạn mã dưới đây là minh hoạ cú pháp dbt 1.x. Con số và hành vi được đối chiếu với docs.getdbt.com; phần nào không chắc thì bài này không khẳng định.

Môi trường & targets: một codebase, nhiều đích

Trái tim của việc tách môi trường trong dbt-core là file profiles.yml. Nó không nằm trong repo project (mặc định ở ~/.dbt/profiles.yml) và chứa thông tin kết nối tới kho dữ liệu. Một profile có thể định nghĩa nhiều target — mỗi target là một "đích" chạy với credential và schema/dataset riêng.

# ~/.dbt/profiles.yml (minh hoạ)
ncb_analytics:
  target: dev            # target mặc định khi không truyền --target
  outputs:
    dev:
      type: postgres
      host: localhost
      user: "{{ env_var('DBT_DEV_USER') }}"
      password: "{{ env_var('DBT_DEV_PASSWORD') }}"
      dbname: analytics
      schema: "dbt_kienpv"   # mỗi dev một schema riêng, tránh giẫm chân nhau
      threads: 4

    prod:
      type: postgres
      host: "{{ env_var('DBT_PROD_HOST') }}"
      user: "{{ env_var('DBT_PROD_USER') }}"
      password: "{{ env_var('DBT_PROD_PASSWORD') }}"
      dbname: analytics
      schema: analytics       # schema production dùng chung
      threads: 8

Chạy với target nào là do cờ --target (viết tắt -t):

# minh hoạ
dbt run                    # dùng target mặc định (dev)
dbt build --target prod    # chạy toàn bộ vào production

Ba nguyên tắc sống còn ở đây:

  1. Tách schema dev theo người dùng. Mỗi thành viên đặt schema riêng (ví dụ dbt_kienpv, dbt_lan) để khi nhiều người cùng phát triển, model của người này không đè bảng của người kia. Trong BigQuery đây là dataset thay vì schema — xem thêm BigQuery overview.
  2. Không bao giờ commit credential. Mọi giá trị nhạy cảm dùng env_var(). Trong dev có thể đặt biến môi trường qua .env; trong CI/production, inject qua secret manager của runner (GitHub Actions secrets, Airflow Connections/Variables, Vault...).
  3. target.name là biến điều kiện. Bên trong model/macro có thể đọc target.name để đổi hành vi — ví dụ giới hạn dữ liệu khi ở dev để chạy nhanh, và quét full khi ở prod.
-- models/staging/stg_don_hang.sql (minh hoạ)
select * from {{ source('app', 'don_hang') }}
{% if target.name != 'prod' %}
  where ngay_tao >= dateadd('day', -3, current_date)  -- dev chỉ lấy 3 ngày
{% endif %}

Chạy production: ba con đường

dbt không tự có scheduler của riêng nó (trừ dbt Cloud). Về bản chất, "chạy production" nghĩa là có một tiến trình nào đó gọi lệnh dbt theo lịch, với credential production, rồi thu thập kết quả. Có ba lựa chọn phổ biến.

1. dbt Cloud

dbt Cloud là sản phẩm managed của chính dbt Labs. Nó cung cấp job scheduler (chạy theo cron, theo API, hoặc sau khi job khác xong), IDE trên trình duyệt, hosting cho docs, và CI tích hợp sẵn với Git provider. Ưu điểm là gần như không phải tự vận hành hạ tầng: bạn cấu hình job "chạy dbt build mỗi 6h vào production" bằng vài cú click, bật thông báo email/Slack khi fail, và có sẵn màn hình theo dõi run. Đây là lựa chọn nhanh nhất để lên production nếu tổ chức chấp nhận SaaS.

2. Airflow (khuyến nghị khi đã có Airflow)

Nếu tổ chức đã vận hành Airflow (như trong series Airflow của chúng ta), điều phối dbt bằng Airflow là lựa chọn tự nhiên: dbt trở thành một phần của bức tranh orchestration tổng thể, dùng chung cơ chế retry, alerting, SLA và production ops đã có.

Có hai mức độ tích hợp:

  • BashOperator gọi lệnh dbt. Cách đơn giản nhất: một task chạy dbt build. Nhược điểm là toàn bộ project dbt là một hộp đen với Airflow — nếu một model fail, cả task đỏ, và trên UI Airflow bạn không thấy lineage từng model.
  • astronomer-cosmos. Thư viện mã nguồn mở render project dbt thành DAG/TaskGroup của Airflow, mỗi model/test thành một task riêng. Nhờ đó bạn thấy lineage dbt ngay trên UI Airflow, retry được từng model, và tận dụng đầy đủ scheduling của Airflow. Cosmos đọc manifest.json (hoặc parse project) để dựng đồ thị task.
# dags/dbt_daily.py — điều phối dbt bằng cosmos (minh hoạ)
from datetime import datetime
from cosmos import DbtDag, ProjectConfig, ProfileConfig, ExecutionConfig
from cosmos.profiles import PostgresUserPasswordProfileMapping

profile = ProfileConfig(
    profile_name="ncb_analytics",
    target_name="prod",
    profile_mapping=PostgresUserPasswordProfileMapping(
        conn_id="postgres_prod",          # dùng Airflow Connection, không hardcode
        profile_args={"schema": "analytics"},
    ),
)

dbt_daily = DbtDag(
    project_config=ProjectConfig("/opt/dbt/ncb_analytics"),
    profile_config=profile,
    execution_config=ExecutionConfig(dbt_executable_path="/opt/venv/bin/dbt"),
    schedule="0 2 * * *",                 # 02:00 hằng ngày
    start_date=datetime(2026, 1, 1),
    catchup=False,
    dag_id="dbt_daily",
)

DbtDag sinh nguyên một DAG từ project; nếu chỉ muốn nhúng dbt vào một DAG lớn hơn (ví dụ sau bước ingest), dùng DbtTaskGroup bên trong một DAG có sẵn.

3. dbt-core + cron / CI runner + container hoá

Ở mức tối giản nhất: đóng gói project dbt vào một Docker image (cài dbt-core + adapter + copy project), rồi cho một scheduler bất kỳ chạy dbt build. Scheduler đó có thể là cron trên một VM, một scheduled job trên Kubernetes (CronJob), hay một scheduled workflow trên GitLab CI/GitHub Actions.

# entrypoint trong container production (minh hoạ)
dbt deps                          # cài packages
dbt build --target prod           # run + test + snapshot + seed theo DAG

Container hoá quan trọng vì nó cố định version của dbt, adapter và packages — tránh cảnh "chạy được trên máy A, fail trên máy B". Nhớ cấu hình pip trong image tin CA của công ty nếu build sau proxy nội bộ.

dbt build — lệnh production điển hình

Điểm chung của cả ba con đường: lệnh production nên là dbt build chứ không phải dbt run. dbt build chạy models, tests, snapshots, seeds trong đúng thứ tự DAG trong một lệnh duy nhất. Điểm mấu chốt: test trên model upstream chặn model downstream — nếu test unique trên stg_don_hang fail, mọi model phụ thuộc nó sẽ SKIP thay vì tiếp tục dựng trên dữ liệu bẩn. So với việc chạy dbt run rồi dbt test tách rời (test chỉ chạy sau khi tất cả model đã dựng xong), dbt build dừng sớm ngay tại chỗ hỏng và sinh một bộ artifact thống nhất.

CI/CD & Slim CI: chỉ chạy phần đổi

Đây là phần đắt giá nhất của bài. Nguyên tắc CI cho dbt: mỗi pull request phải chứng minh code mới build được và pass test — trước khi merge. Cách ngây thơ là mỗi PR chạy dbt build toàn bộ project. Với vài chục model thì ổn; với vài nghìn model trên kho dữ liệu tính tiền theo lượng quét (như BigQuery), mỗi PR full-build là chậm và tốn kém đến mức không dùng nổi.

Slim CI giải bài toán này bằng state comparison: chỉ build & test những model đã đổi trong PR, cộng với downstream của chúng.

Cơ chế state

Mỗi lần chạy, dbt sinh ra artifacts — trong đó manifest.json là "ảnh chụp" toàn bộ project đã parse (định nghĩa model, config, quan hệ phụ thuộc). Ý tưởng: lưu lại manifest.json của lần chạy production thành công gần nhất, rồi khi CI chạy trên PR, dbt so sánh manifest hiện tại với manifest production đó để biết cái gì đã đổi.

  • state:modified — chọn các model có metadata khác so với state tham chiếu (code đổi).
  • Toán tử + phía sau (state:modified+) — kéo theo toàn bộ downstream của các model đổi, vì đổi một staging có thể làm sai mọi mart phía dưới.
# CI trên PR: chỉ build phần đổi + downstream (minh hoạ)
dbt build \
  --select state:modified+ \
  --defer \
  --state ./prod-artifacts     # thư mục chứa manifest.json từ prod

Vai trò của --defer: với những model không đổi mà model đang build cần tham chiếu tới, thay vì dựng lại chúng trong schema CI, dbt "defer" (trỏ) sang bảng production đã có. Nhờ vậy CI chỉ vật chất hoá đúng những gì thay đổi, trong một schema tạm riêng biệt, và không đụng dữ liệu prod.

Nguồn của state tham chiếu

Điểm dễ vấp: prod-artifacts ở đâu ra? Trong pipeline tự dựng, bạn cho job production upload manifest.json (và run_results.json) lên một nơi bền vững (S3/GCS/artifact store) sau mỗi lần chạy thành công; job CI tải nó về trước khi chạy --state. Trong dbt Cloud, chuyện này được lo sẵn qua khái niệm "deferral environment" — CI job tự so sánh với environment production.

Một cảnh báo từ docs: nếu manifest tham chiếu bị lệch/cũ (ví dụ nhiều job cùng ghi đè artifacts với cấu hình khác nhau), CI có thể chọn nhầm model. Cách xử lý thông dụng là rebase/merge nhánh base mới nhất vào PR để làm mới state.

Artifacts: manifest.json & run_results.json

Hai artifact cốt lõi, nằm trong thư mục target/ sau mỗi lệnh:

  • manifest.json — cấu trúc project sau parse: mọi node (model, test, source, snapshot), config, và đồ thị phụ thuộc. Đây là input cho state comparison, cho lineage/docs, và cho các công cụ ngoài (như cosmos) đọc DAG.
  • run_results.json — kết quả thực thi lần chạy vừa rồi: mỗi node pass/fail/skip, thời gian chạy, thông báo lỗi. Đây là nguồn vàng cho quan sát: model nào chậm dần, test nào hay fail.

Hai file này chính là lý do dbt "biết" được nhiều thứ về chính nó — chúng biến metadata vận hành thành dữ liệu có thể phân tích.

Orchestration & thứ tự phụ thuộc

Một pipeline dbt production hoàn chỉnh không chỉ có mỗi dbt build. Thứ tự hợp lý:

  1. dbt source freshness trước tiên — kiểm tra dữ liệu nguồn có mới không. Nếu nguồn chưa cập nhật, có khi nên dừng sớm thay vì dựng lại toàn bộ trên dữ liệu cũ.
  2. Snapshots (nếu tách riêng) — chốt trạng thái SCD trước khi transform, để không bỏ lỡ thay đổi.
  3. dbt build — run + test theo DAG.
  4. Retry & alert — cấu hình retry ở tầng orchestrator (Airflow: retries, retry_delay), và thông báo khi fail (Slack/email). Một pipeline im lặng khi hỏng còn nguy hiểm hơn không có pipeline.

Sơ đồ dưới đây gộp cả hai vế của bài: nhánh trái là CI trên PR (chỉ phần đổi), nhánh phải là production hằng ngày qua Airflow.

Chú ý mũi tên đứt nối hai vòng: production sinh ra artifacts làm state cho CI, còn CI gác cổng cho code đi vào production — một vòng khép kín.

Best practices tổ chức project

Deployment chỉ mượt khi project được tổ chức kỷ luật. Những quy ước đã kiểm chứng theo docs của dbt:

  • Phân tầng rõ ràng: staging → intermediate → marts. Staging làm sạch/đổi tên 1-1 với source; intermediate chứa logic trung gian tái sử dụng; marts là bảng phục vụ BI. Ranh giới rõ giúp state comparison và lineage có nghĩa.
  • Một staging cho một source. Mỗi bảng nguồn có đúng một model staging là "cửa vào" duy nhất; nơi khác không select thẳng từ source.
  • Quy ước đặt tên nhất quán. stg_, int_, dim_, fct_... Tên đoán được giúp cả người và công cụ định hướng.
  • Test khoá chính + relationships. Mọi model nên có test unique + not_null trên khoá, và relationships giữa fact và dimension (xem lại Tests & docs). Đây là phần chặn downstream trong dbt build.
  • Tài liệu + exposures. Mô tả model/cột, và khai báo exposures để lineage chạy tới tận dashboard/report — biết đổi model này sẽ ảnh hưởng báo cáo nào.
  • Review PR nghiêm túc. Bắt buộc review, chạy Slim CI xanh mới merge; nếu dùng Looker, review cả LookML đi kèm.
  • Giới hạn quyền production. Credential prod chỉ nằm trong secret của scheduler/CI. Dev không có quyền ghi trực tiếp vào schema production; mọi thay đổi prod đi qua pipeline, không qua tay người.

Quan sát: biết pipeline đang khoẻ hay ốm

Khi dbt chạy tự động, bạn cần thấy nó chạy thế nào:

  • Thời gian chạy & chi phí. Theo dõi tổng thời gian job và (trên kho tính tiền theo quét) lượng byte scan. Xu hướng tăng dần là tín hiệu sớm.
  • Model chậm. run_results.json cho biết node nào ngốn thời gian nhất — ứng viên để chuyển sang incremental.
  • Test fail. Đếm và cảnh báo test hay đỏ; test fail lặp lại thường lộ vấn đề upstream thật sự.
  • Công cụ. Package elementary (dbt package) parse artifacts để dựng dashboard chất lượng dữ liệu và cảnh báo bất thường; ngoài ra có thể tự nạp run_results.json vào một bảng "dbt runs" để phân tích bằng chính dbt.

Use case thực tế: pipeline dbt hằng ngày + Slim CI trên PR

Đặt tất cả lại với nhau cho một đội analytics dùng PostgreSQL/BigQuery và đã có Airflow:

Production hằng ngày. Một DbtDag (cosmos) chạy 02:00: dbt source freshness → nếu nguồn OK thì dbt snapshotdbt build --target prod, mỗi model/test là một task trên Airflow. Retry 2 lần cho task fail, alert Slack nếu vẫn đỏ. Sau khi build thành công, một task cuối upload target/manifest.jsonrun_results.json lên GCS — đây là state cho CI và là dữ liệu cho quan sát.

Slim CI trên mỗi PR. GitHub Actions kích hoạt khi có PR: checkout code → dbt depstải manifest.json prod từ GCSdbt build --select state:modified+ --defer --state ./prod-artifacts vào một schema CI tạm. Chỉ model đổi + downstream được build và test; model không đổi trỏ về bảng prod nhờ --defer. CI xanh mới cho merge. PR đổi một model chỉ mất vài phút thay vì full-build hàng giờ.

Vòng khép kín. Code merge → lần chạy prod kế tiếp áp dụng → prod sinh manifest mới → CI của PR tiếp theo so với state mới nhất. Deployment, kiểm thử và quan sát ăn khớp thành một hệ.

Ghi nhớ

  • profiles.yml + --target tách dev/prod; mỗi dev một schema riêng; credential luôn qua env_var(), không bao giờ commit.
  • Lệnh production điển hình là dbt build — run + test + snapshot + seed theo DAG, và test upstream chặn downstream.
  • Ba con đường chạy production: dbt Cloud (managed, có sẵn scheduler + CI), Airflow (BashOperator hoặc astronomer-cosmos cho lineage từng model), dbt-core + cron/CI + container.
  • Slim CI = state:modified+ + --defer --state <prod artifacts>: chỉ build & test phần đổi + downstream → CI nhanh, rẻ, an toàn.
  • manifest.json (cấu trúc + state) và run_results.json (kết quả chạy) là nền cho state comparison, lineage và quan sát.
  • Orchestrate đúng thứ tự: freshness → snapshot → build, có retry + alert.
  • Kỷ luật tổ chức (phân tầng, 1 staging/1 source, test khoá + relationships, docs/exposures, review PR, giới hạn quyền prod) là điều kiện để deployment bền vững.

Đến đây series dbt khép lại: bạn đã có đủ mảnh ghép từ mô hình hoá tới đưa vào production. Bước tiếp theo tự nhiên là nối dbt với phần điều phối tổng thể ở Airflow production ops, và tối ưu kho đích ở BigQuery.

Bài viết liên quan

CSV/JSON/Parquet/Avro, lưu trữ theo hàng vs cột, nén, và OLTP vs OLAP.

13 thg 7, 2026 8

Data Engineering là gì, vai trò trong vòng đời dữ liệu, và bức tranh hệ sinh thái công cụ.

13 thg 7, 2026 5

Vì sao xử lý phân tán, mô hình Spark (RDD/DataFrame), lazy evaluation, shuffle và tối ưu.

13 thg 7, 2026 5

Đảm bảo chất lượng & minh bạch dữ liệu bằng dbt: data tests dựng sẵn (unique, not_null, relationships, accepted_values), singular test, unit test, và tài liệu tự sinh kèm lineage graph.

13 thg 7, 2026 5