DataOps nâng cao 4 — CI/CD đầu-cuối cho analytics
Từ "chạy dbt trên máy tôi" tới dây chuyền tự động
Bài DataOps 5 — CI/CD & tự động hoá đã dựng nguyên tắc chung. Bài này đi sâu hơn một bậc: thực chiến pipeline dbt + Git cụ thể, dán được vào repo NCB và chạy ngay. Nếu bạn cần nền tảng CI/CD phần mềm, xem DevOps CI/CD; còn ở đây ta tập trung vào tài sản analytics: model dbt, data test, và Airflow DAG.
Bối cảnh ngân hàng đặt ra ràng buộc rõ ràng: mọi thay đổi logic báo cáo — công thức phân loại nợ, quy tắc tính lãi, số liệu CAR — phải qua CI test và phê duyệt trước khi lên prod. Không ai được sửa tay trên prod. Mỗi thay đổi phải an toàn (không phá số liệu đang chạy) và truy vết được (biết ai đổi gì, khi nào, PR nào). CI/CD chính là cơ chế biến những yêu cầu tuân thủ đó thành cổng chặn tự động thay vì lời hứa của con người.
Git workflow: trunk-based cho analytics
Ta dùng trunk-based development: một nhánh chính main luôn ở trạng thái deploy được, mọi thay đổi đi qua feature branch ngắn hạn:
- Analyst tạo nhánh
feat/npl-recalctừmain. - Sửa model, commit, push.
- Mở Pull Request → CI tự chạy.
- Review: người thứ hai đọc diff, đối chiếu logic nghiệp vụ.
- CI xanh + đã approve → merge vào
main(squash để lịch sử sạch).
Nhánh sống ngắn (giờ/ngày, không phải tuần) để tránh xung đột merge và "big-bang deploy". Đây là điểm khác với git-flow nhiều nhánh dài; nền tảng Git cho analytics xem thêm ở DataOps 3 — Git.
Ánh xạ môi trường
Mỗi tầng Git ứng với một môi trường warehouse riêng. Với dbt, khác biệt chỉ là target/schema, cùng một codebase:
| Môi trường | Ai dùng | Schema đích | Dữ liệu | Khi nào build |
|---|---|---|---|---|
| dev | mỗi dev/branch | dbt_<user> hoặc ci_pr_<n> | sample/test | khi dev chạy tay, và trong CI |
| staging | team | analytics_staging | bản sao ẩn danh gần prod | khi merge main |
| prod | báo cáo thật | analytics | dữ liệu thật | sau khi approve |
Điểm mấu chốt: dev có schema riêng cho từng người/từng PR (dbt_ha, ci_pr_412), nên hai người sửa cùng model không giẫm lên nhau, và CI của mỗi PR dựng bảng vào schema tạm rồi xoá sau — không đụng vào bảng thật.
Continuous Integration: cổng chặn trên mỗi PR
Mục tiêu CI: không một dòng SQL lỗi nào lọt vào main. Chuỗi bước điển hình, chạy tuần tự, fail bất kỳ bước nào là chặn merge:
| # | Bước | Lệnh | Bắt lỗi gì |
|---|---|---|---|
| 1 | Checkout | actions/checkout | — |
| 2 | Cài dbt | pip install dbt-postgres | — |
| 3 | Nạp package | dbt deps | thiếu package, version lệch |
| 4 | Lint SQL | sqlfluff lint models/ | sai style, tên bảng lộn xộn |
| 5 | Compile | dbt compile | ref sai, macro hỏng |
| 6 | Slim CI build | dbt build --select state:modified+ | model đổi + downstream trên dữ liệu test |
| 7 | Data test | (nằm trong build) | vi phạm unique/not_null/quan hệ |
| 8 | dbt-checkpoint | pre-commit run | thiếu test/mô tả theo quy ước |
| 9 | Báo lên PR | check status | chặn merge nếu fail |
Slim CI — chỉ build phần đổi & downstream
Chạy lại toàn bộ warehouse mỗi PR thì chậm và tốn. Slim CI dùng cơ chế state-aware của dbt: so sánh với manifest.json của lần build production gần nhất để biết model nào đã đổi, rồi chỉ build những model đó cộng downstream.
# So state với manifest prod đã lưu artifact
dbt build --select state:modified+ --defer --state ./prod-artifacts
state:modified+ = "các model thay đổi và mọi thứ phía sau"; --defer = model không đổi thì trỏ thẳng tới bảng có sẵn ở prod thay vì dựng lại. Một PR sửa 1 model trong dự án 400 model có thể chỉ build 6 model — CI từ ~40 phút xuống ~2 phút. Chi tiết cơ chế state xem dbt 7 — deployment & CI.
Data test là bất biến chạy trong CI
Ngoài dbt test (unique, not_null, relationships), ta viết các truy vấn kiểm tra bất biến (invariant) — một SELECT trả về các dòng vi phạm; có dòng nào là CI fail. Ví dụ bất biến "không tài khoản VND nào được có số dư âm", chạy trên sandbox:
-- ▶ Chạy được
SELECT a.id AS account_id, a.account_no, a.balance, a.currency,
c.full_name
FROM accounts a
JOIN customers c ON c.id = a.customer_id
JOIN transactions t ON t.account_id = a.id
JOIN employees e ON e.id = c.id
JOIN departments d ON d.id = e.department_id
WHERE a.currency = 'VND' AND a.balance < 0;
Trong dbt, câu này thường là một singular test đặt trong tests/: dbt bọc nó thành SELECT COUNT(*) ... và fail khi count > 0. CI chạy nó tự động trên mỗi PR nên không ai phải nhớ chạy tay. (Lưu ý: các phần join employees/departments ở trên chỉ để minh hoạ truy vấn nhiều bảng trên sandbox; test thật chỉ cần accounts + customers.)
Continuous Deployment: từ merge tới prod
Khi PR merge vào main, dây chuyền CD chạy theo trình tự an toàn, không nhảy thẳng vào prod:
merge main → deploy staging → smoke test → chờ approve → deploy prod → tag → theo dõi
- Deploy staging (
dbt build --target staging): dựng toàn bộ hoặc phần đổi vàoanalytics_stagingvới dữ liệu gần-prod. - Smoke test: vài truy vấn nhanh xác nhận số liệu trọng yếu hợp lý (tổng dư nợ không âm, số dòng bảng chính không tụt 50%).
- Phê duyệt (approval gate): với dữ liệu ngân hàng đây là manual approval bắt buộc — một reviewer có thẩm quyền bấm duyệt trong GitHub Environment
productiontrước khi bước prod chạy. - Deploy prod (
dbt build --target prod): build vàoanalytics. - Tag commit (
v2026.07.08) để cột số liệu prod với đúng một commit → truy vết được.
Blue-green & rollback cho warehouse
Để deploy không làm gãy báo cáo đang đọc, dùng blue-green: build phiên bản mới vào schema analytics_green, chạy smoke test, rồi swap (đổi tên schema / trỏ view) sang cho báo cáo. Nếu hỏng, rollback = trỏ lại schema analytics_blue cũ — gần như tức thì, không phải chạy lại pipeline. Vì mỗi deploy gắn một tag, rollback code chỉ là git revert rồi re-deploy.
Deploy Airflow DAG như code
DAG cũng là code trong repo. CD không sửa DAG trên Airflow UI mà sync file .py từ Git vào thư mục dags/ của môi trường tương ứng (git-sync sidecar, hoặc CI đẩy lên bucket mà scheduler đọc). Nhờ vậy DAG đang chạy luôn khớp bản trong Git; chi tiết vận hành xem Airflow 7 — production ops.
Secret & connection theo môi trường
Không hardcode mật khẩu warehouse trong repo. Mỗi môi trường có bộ credential riêng, nạp lúc runtime. Cách hiện đại nhất là OIDC: GitHub Actions xin token ngắn hạn trực tiếp từ cloud (AWS/GCP) qua liên kết tin cậy, không cần lưu access key tĩnh nào trong repo.
permissions:
id-token: write # cho phép OIDC
contents: read
# ...
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/dbt-ci-prod
aws-region: ap-southeast-1
Connection string / mật khẩu Postgres thì lưu ở GitHub Environment secrets tách theo staging/production, để job staging không bao giờ chạm được credential prod.
Sơ đồ pipeline CI/CD
Ví dụ GitHub Actions gần hoàn chỉnh
Hai workflow: ci.yml chạy trên PR, cd.yml chạy khi push main. YAML/bash dưới đây là minh hoạ để tham khảo (không phải SQL chạy trên sandbox).
# .github/workflows/ci.yml — chạy trên mỗi PR
name: dbt-ci
on:
pull_request:
branches: [main]
jobs:
build-test:
runs-on: ubuntu-latest
environment: ci
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.11" }
- name: Cài dbt + sqlfluff
run: pip install dbt-postgres==1.8.* sqlfluff==3.*
- name: dbt deps
run: dbt deps
- name: Lint SQL
run: sqlfluff lint models/ --dialect postgres
- name: dbt compile
run: dbt compile --target ci
env:
DBT_PG_PASS: ${{ secrets.CI_PG_PASS }}
# Tải manifest của build prod gần nhất để so state
- name: Lấy prod manifest
run: aws s3 cp s3://ncb-dbt-artifacts/prod/manifest.json ./prod-artifacts/manifest.json
- name: Slim CI build + test
run: |
dbt build --target ci \
--select state:modified+ \
--defer --state ./prod-artifacts
env:
DBT_PG_PASS: ${{ secrets.CI_PG_PASS }}
- name: dbt-checkpoint (quy ước)
run: pre-commit run --all-files
# .github/workflows/cd.yml — chạy khi merge vào main
name: dbt-cd
on:
push:
branches: [main]
jobs:
staging:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.11" }
- run: pip install dbt-postgres==1.8.*
- run: dbt deps
- name: Build staging
run: dbt build --target staging
env:
DBT_PG_PASS: ${{ secrets.STAGING_PG_PASS }}
- name: Smoke test
run: dbt test --target staging --select tag:smoke
production:
needs: staging
runs-on: ubuntu-latest
environment: production # yêu cầu manual approval
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/dbt-ci-prod
aws-region: ap-southeast-1
- uses: actions/setup-python@v5
with: { python-version: "3.11" }
- run: pip install dbt-postgres==1.8.*
- run: dbt deps
- name: Build production
run: dbt build --target prod
- name: Lưu manifest cho slim CI lần sau
run: aws s3 cp target/manifest.json s3://ncb-dbt-artifacts/prod/manifest.json
- name: Tag release
run: |
git tag "v$(date +%Y.%m.%d)-${GITHUB_SHA::7}"
git push --tags
Cấu hình branch protection trên main: bắt buộc status check dbt-ci xanh + ít nhất 1 approval mới cho merge. Trên GitHub Environment production: bật Required reviewers để chặn bước prod cho tới khi có người duyệt.
Đo DORA cho analytics
Bốn chỉ số DORA áp được cho analytics, giúp đội tự đánh giá "chạy tốt hay không":
| Chỉ số | Ý nghĩa cho analytics | Nguồn đo |
|---|---|---|
| Deployment frequency | bao lần lên prod / tuần | số lần workflow cd thành công |
| Lead time for changes | từ commit tới prod bao lâu | thời gian commit → tag prod |
| Change failure rate | % deploy gây lỗi số liệu | số rollback / tổng deploy |
| Time to restore | sửa xong bao lâu sau sự cố số liệu | mở → đóng incident |
Với blue-green, time to restore rất thấp vì rollback chỉ là swap schema. Một đội analytics khoẻ mạnh thường deploy hằng ngày, lead time dưới nửa ngày, change failure rate một chữ số phần trăm.
Use case thực tế
Đổi công thức phân loại nợ xấu (NPL) cho báo cáo NHNN. Nghiệp vụ yêu cầu chỉnh ngưỡng ngày quá hạn của nhóm nợ nhóm 3 từ 90 sang 91 ngày, ảnh hưởng model fct_loan_classification và 4 model downstream (tỷ lệ NPL, trích lập dự phòng).
Trước đây quy trình là: mở SQL client sửa tay trên prod cuối tuần, không ai review, tuần sau phát hiện tỷ lệ NPL toàn hàng sai vì quên cập nhật một view — không biết deploy nào gây ra.
Với pipeline mới:
- Analyst tạo nhánh
feat/npl-91d, sửa macro ngưỡng, mở PR. - CI chạy 2 phút:
sqlfluffxanh,dbt build --select state:modified+build đúng 5 model đổi + downstream vào schemaci_pr_412, 37 data test pass (gồm bất biến "tổng dư nợ 5 nhóm = tổng danh mục"). - Trưởng nhóm rủi ro đọc diff, đối chiếu công văn NHNN, approve.
- Merge → staging build, smoke test so tỷ lệ NPL staging lệch < 0.3% so kỳ vọng.
- Giám đốc dữ liệu bấm duyệt trong Environment
production. - Deploy blue-green vào
analytics_green, swap, tagv2026.07.08-a1b2c3d.
Kết quả: thay đổi lên prod trong cùng ngày, có PR + approval làm dấu vết kiểm toán, và khi kiểm toán nội bộ hỏi "ai đổi ngưỡng NPL, dựa vào đâu", câu trả lời là link PR #412 kèm review — đúng yêu cầu truy vết của ngân hàng.
Ghi nhớ
- Trunk-based + PR ngắn hạn:
mainluôn deploy được; mọi thay đổi qua feature branch → PR → review → merge. - Ánh xạ môi trường bằng dbt target/schema: dev (schema riêng mỗi dev/PR) → staging → prod, cùng một codebase.
- CI trên mỗi PR: checkout → dbt deps → sqlfluff → compile → slim CI
state:modified+→ dbt test → dbt-checkpoint → chặn merge nếu fail. - Slim CI chỉ build model đổi + downstream nhờ so
manifest.json; PR nhỏ build vài model thay vì cả warehouse. - CD đi tuần tự: merge → staging → smoke → manual approval → prod; blue-green để swap/rollback tức thì; tag mỗi release.
- Không hardcode secret: OIDC token ngắn hạn + Environment secrets tách staging/prod; DAG deploy như code, không sửa trên UI.
- Ngân hàng: mọi đổi logic báo cáo phải qua CI test + approval trước prod → an toàn và truy vết được.
- Đo DORA (frequency, lead time, change failure rate, time to restore) để biết đội analytics vận hành có khoẻ không.
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.
Data Engineering là gì, vai trò trong vòng đời dữ liệu, và bức tranh hệ sinh thái công cụ.
Vì sao xử lý phân tán, mô hình Spark (RDD/DataFrame), lazy evaluation, shuffle và tối ưu.
Đả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.