DataOps nâng cao 8 — Case study ngân hàng end-to-end
Từ 7 mảnh ghép tới một hệ thống báo cáo chạy thật
Bảy bài trước của series dựng từng năng lực riêng lẻ: kiến trúc, dbt medallion, Airflow, CI/CD, data quality, observability, platform. Bài cuối này ghép tất cả vào một tình huống thật: xây pipeline DataOps end-to-end cho hệ thống báo cáo giám sát NHNN của NCB — cụ thể là báo cáo phân loại nợ và tỷ lệ NPL toàn hàng, loại báo cáo mà một con số sai có thể dẫn tới xử phạt hành chính.
Đây là bài tổng kết nên ta không giới thiệu kỹ thuật mới. Thay vào đó ta đi dọc pipeline, mỗi chặng chỉ ra bài nào của series giải quyết mắt xích đó, rồi dừng lâu ở phần khác biệt cốt lõi của ngân hàng so với công ty tech thông thường: tuân thủ và kiểm soát.
Bối cảnh: NCB có core banking sinh ~2 triệu bút toán/ngày, phòng Rủi ro cần báo cáo phân loại nợ (nhóm 1–5), tỷ lệ NPL, số trích lập dự phòng, chốt sổ hằng ngày và tổng hợp theo kỳ để nộp NHNN. Trước dự án: một analyst chạy loạt query tay trên bản sao core, ghép Excel, mất 2–3 ngày mỗi kỳ, không ai review công thức, không truy được số liệu về nguồn khi thanh tra hỏi.
Bản đồ 8 bài trong một pipeline
Trước khi đi vào chi tiết, đây là cách 8 bài của series khớp vào các chặng của pipeline:
| Chặng pipeline | Bài giải quyết | Vai trò trong case |
|---|---|---|
| Kiến trúc tổng thể lakehouse + ELT | Bài 1 — Kiến trúc | Lakehouse trên object storage, tách compute/storage, ELT thay ETL |
| Biến đổi dữ liệu theo medallion | Bài 2 — dbt & ELT | bronze → silver → gold, mart báo cáo bằng dbt |
| Điều phối cuối ngày + backfill | Bài 3 — Airflow | DAG chốt sổ EOD, chạy lại kỳ cũ |
| Đưa thay đổi lên prod an toàn | Bài 4 — CI/CD | PR → CI test → approval → deploy |
| Chặn số sai trước khi phục vụ | Bài 5 — Quality & contract | data test + contract giữa domain |
| Biết khi nào hỏng & khôi phục | Bài 6 — Observability | freshness, SLO, quy trình incident |
| Domain tự phục vụ | Bài 7 — Platform self-service | template + guardrail cho phòng Rủi ro |
Phần còn lại của bài đi dọc theo bảng này, rồi bổ sung lớp governance/audit bao quanh — thứ không phải công ty tech nào cũng cần nhưng ngân hàng thì bắt buộc.
Chặng 1–2: từ core banking tới mart báo cáo
Kiến trúc (bài 1). Dữ liệu core banking được đưa vào lakehouse trên object storage qua CDC (change data capture): mỗi thay đổi ở bảng core (khoản vay, lịch trả nợ, số dư) chảy vào tầng bronze dạng file cột (Parquet/Delta). Compute (Spark/warehouse) tách khỏi storage nên chốt sổ nặng không đụng core đang giao dịch.
dbt medallion (bài 2). Trên lakehouse, dbt biến đổi theo ba tầng:
- bronze — bản thô CDC, giữ nguyên như nguồn để đối chiếu.
- silver — làm sạch, chuẩn hoá: chuẩn hoá mã khách, gắn số ngày quá hạn cho từng khoản vay, ép kiểu, khử trùng lặp CDC.
- gold (mart) — chỉ tiêu nghiệp vụ:
fct_loan_classification(phân nhóm nợ 1–5),mart_npl_summary(tỷ lệ NPL theo chi nhánh/kỳ),mart_provision(trích lập).
Chỉ tiêu tổng hợp mà báo cáo NHNN đọc nằm ở tầng gold. Dưới đây là một mart tổng hợp minh hoạ chạy được trên sandbox — ta dùng bộ bảng demo (accounts/customers/transactions) để minh hoạ hình dạng của một mart "tổng hợp theo nhóm + theo đơn vị", còn project thật thì các bảng này là ref() tới silver:
-- ▶ Chạy được
-- Minh hoạ mart tổng hợp báo cáo: phân "nhóm" theo số dư + tổng hợp theo thành phố (đơn vị)
-- Thật: thay bằng phân nhóm nợ theo số ngày quá hạn, group theo chi nhánh/kỳ
WITH classified AS (
SELECT
a.id AS account_id,
a.customer_id,
a.balance,
a.currency,
CASE
WHEN a.balance >= 1000000000 THEN 'nhom_1'
WHEN a.balance >= 100000000 THEN 'nhom_2'
WHEN a.balance >= 0 THEN 'nhom_3'
ELSE 'nhom_4'
END AS risk_bucket
FROM accounts a
WHERE a.currency = 'VND'
)
SELECT
c.city AS don_vi,
cl.risk_bucket,
COUNT(*) AS so_tai_khoan,
SUM(cl.balance) AS tong_du_no,
ROUND(AVG(cl.balance)::numeric, 2) AS du_no_binh_quan,
ROUND(100.0 * COUNT(*)
/ SUM(COUNT(*)) OVER (PARTITION BY c.city)
, 2) AS ty_le_pct
FROM classified cl
JOIN customers c ON c.id = cl.customer_id
GROUP BY c.city, cl.risk_bucket
ORDER BY c.city, cl.risk_bucket;
Câu này cho ra cấu trúc y hệt một mart giám sát thật: mỗi đơn vị × mỗi nhóm rủi ro có số lượng, tổng dư nợ, và tỷ lệ phần trăm trong đơn vị — chính là hình dạng bảng nộp NHNN.
Chặng 3: điều phối chốt sổ cuối ngày & backfill
Airflow (bài 3). Một DAG eod_regulatory_reporting chạy sau khi core đóng ngày:
- Chờ sensor báo CDC của ngày đã đầy đủ (không chạy trên dữ liệu thiếu).
dbt build --select tag:silver→ làm sạch.dbt build --select tag:gold→ dựng mart báo cáo.dbt test --select tag:report→ chạy data test đối soát (chặng 5).- Xuất bảng nộp + snapshot số liệu kỳ.
Điểm ngân hàng đặc biệt quan tâm là backfill có kiểm soát: khi NHNN yêu cầu chạy lại báo cáo một kỳ cũ, DAG phải chạy lại đúng logic và đúng dữ liệu của kỳ đó, không dùng logic hôm nay. Đây là chỗ nối sang reproducibility ở phần tuân thủ bên dưới.
Chặng 4–5: đưa lên prod an toàn & chặn số sai
CI/CD (bài 4). Mọi thay đổi công thức báo cáo — đổi ngưỡng ngày quá hạn, đổi cách tính dự phòng — đi qua PR: CI chạy sqlfluff + dbt build --select state:modified+ trên schema tạm + data test, rồi manual approval của trưởng phòng Rủi ro trước khi lên prod. Không ai sửa tay trên prod.
Data quality + contract (bài 5). Trước khi mart báo cáo được "phục vụ", một loạt data test đối soát phải xanh. Ví dụ bất biến sống-còn của báo cáo phân loại nợ: tổng dư nợ của tất cả các nhóm phải bằng tổng danh mục — nếu lệch nghĩa là có khoản vay rơi khỏi phân loại. Test viết dưới dạng truy vấn trả về các dòng vi phạm; có dòng nào là fail và DAG dừng, không phục vụ số sai. Minh hoạ trên sandbox (một khoản không rơi vào nhóm nào sẽ lộ ra):
-- ▶ Chạy được
-- Data-test đối soát: liệt kê tài khoản VND KHÔNG được phân vào nhóm nào
-- (bất biến: mọi khoản phải thuộc đúng 1 nhóm). Trả 0 dòng = pass.
SELECT
a.id AS account_id,
a.account_no,
a.balance,
c.full_name,
c.city
FROM accounts a
JOIN customers c ON c.id = a.customer_id
WHERE a.currency = 'VND'
AND NOT (
a.balance >= 1000000000
OR a.balance >= 100000000
OR a.balance >= 0
OR a.balance < 0
);
Trong dbt đây là một singular test trong tests/: dbt bọc thành SELECT COUNT(*) và fail khi count > 0. Ngoài ra dùng contract giữa domain: mart báo cáo khai báo schema cố định (tên cột, kiểu, not_null) nên nếu ai đó vô tình đổi kiểu cột tong_du_no, dbt build fail ngay tại chặng silver, không để lỗi lan tới báo cáo.
Chặng 6–7: biết khi hỏng & để domain tự phục vụ
Observability (bài 6). Mỗi mart báo cáo có SLO freshness: "mart NPL phải sẵn sàng trước 8h sáng ngày làm việc". Nếu CDC trễ hoặc test fail, cảnh báo bắn cho on-call trước khi phòng Rủi ro mở báo cáo. Khi có sự cố số liệu, đội theo quy trình incident chuẩn: phát hiện → khoanh vùng bằng lineage → sửa → hậu kiểm (post-mortem không đổ lỗi). Kết hợp blue-green từ bài 4, time to restore rất thấp.
Platform self-service (bài 7). Phòng Rủi ro không phải chờ đội data trung tâm cho mọi thay đổi. Họ dùng template + guardrail: tạo model mới từ khuôn mẫu đã cài sẵn quy ước đặt tên, test bắt buộc, và ràng buộc truy cập. Đội trung tâm cung cấp "đường ray"; domain chạy trên đó — vừa nhanh vừa không phá kiểm soát.
Trọng tâm ngân hàng: tuân thủ & kiểm soát
Đây là điểm khác biệt then chốt so với DataOps ở công ty tech. Bảy chặng trên cho tốc độ; lớp dưới đây cho quyền được vận hành trong ngành ngân hàng.
Quản lý thay đổi & maker-checker
Logic báo cáo là tài sản chịu kiểm soát. Mọi thay đổi tuân maker-checker (người làm ≠ người duyệt): analyst (maker) mở PR sửa công thức; một người có thẩm quyền khác (checker) đối chiếu diff với công văn/thông tư NHNN rồi mới approve. Không có PR self-approve cho logic báo cáo giám sát. Cơ chế này chính là CI/CD approval gate ở bài 4, nhưng gắn với nghĩa vụ pháp lý.
Audit trail & lineage đầu-cuối
Khi thanh tra chỉ vào một ô trong báo cáo NHNN hỏi "con số này ở đâu ra", đội phải truy ngược được: ô báo cáo → mart → model silver → bảng bronze → bản ghi CDC → bút toán core, kèm công thức nào (commit nào) đã tính nó. Đây là column-level lineage đầu-cuối. Kết hợp:
- Lineage kỹ thuật từ dbt (graph
ref/source) + catalog — xem Catalog & lineage. - Audit trail của thay đổi logic từ Git: mỗi số liệu prod gắn một commit + PR + người duyệt.
Nhờ vậy câu trả lời cho thanh tra không phải "để em kiểm tra lại" mà là một đường dẫn truy vết đầy đủ.
Reproducibility — chạy lại kỳ cũ ra đúng số cũ
Yêu cầu khắc nghiệt của ngân hàng: chạy lại báo cáo kỳ 6/2025 hôm nay phải ra đúng con số đã nộp 6/2025, kể cả khi công thức đã đổi từ đó. Để đạt được:
- Versioning code: mỗi kỳ nộp gắn một Git tag (
v2025.06-report). Backfill checkout đúng tag đó. - Snapshot dữ liệu nguồn: giữ ảnh bronze của kỳ (time-travel của Delta / bảng snapshot) để chạy lại trên đúng dữ liệu, không phải dữ liệu đã bị chỉnh sau này.
- Versioning quy định: khi luật thay đổi (thông tư mới đổi cách phân nhóm), giữ cả hai phiên bản logic và chọn theo kỳ báo cáo — logic cũ cho kỳ cũ, logic mới cho kỳ mới.
Bảo mật & môi trường tách biệt
Dữ liệu khách hàng (CIF, số dư, khoản vay) là dữ liệu cá nhân nhạy cảm theo Nghị định 13/2023 (NĐ13). Áp dụng: masking/ẩn danh ở môi trường non-prod, kiểm soát truy cập theo vai trò (analyst thấy mart tổng hợp, không thấy PII thô), log truy cập. Môi trường dev/staging/prod tách biệt với credential riêng, staging dùng dữ liệu ẩn danh gần-prod. Chi tiết nghĩa vụ bảo mật xem Privacy & compliance.
Sơ đồ kiến trúc DataOps ngân hàng end-to-end
Lưu ý lớp governance/audit/lineage bao quanh toàn bộ — đó là đặc trưng ngân hàng:
Đo lường kết quả: trước và sau
DataOps chỉ có giá trị nếu đo được. NCB (minh hoạ) dùng DORA cho data cộng chỉ số riêng ngành:
| Chỉ số | Trước dự án | Sau dự án |
|---|---|---|
| Thời gian đóng sổ báo cáo / kỳ | 2–3 ngày, thủ công | ~2 giờ, tự động |
| Deployment frequency (đổi logic lên prod) | vài lần/quý, sửa tay | hằng ngày khi cần, qua CI |
| Lead time (commit → prod) | không đo được | < nửa ngày |
| Change failure rate | cao, không truy được | một chữ số %, có rollback |
| Data test đối soát | 0 (kiểm bằng mắt) | 40+ test tự động, chặn số sai |
| Truy vết cho thanh tra | thủ công, nhiều ngày | link PR + lineage tức thì |
Con số đắt giá nhất với lãnh đạo không phải tốc độ mà là truy vết: khi kiểm toán hỏi, thời gian trả lời từ "vài ngày" xuống "vài phút".
Lộ trình triển khai: crawl → walk → run
Đừng làm tất cả cùng lúc. Ngân hàng phải cân bằng đổi mới vs kiểm soát — làm quá nhanh thì kiểm soát nội bộ chặn; làm quá chậm thì mãi thủ công.
- Crawl (tháng 1–3): đưa một báo cáo lên dbt medallion + Git + CI test cơ bản. Mục tiêu: bỏ sửa tay trên prod, có PR review. Chưa cần lineage đẹp.
- Walk (tháng 4–9): thêm Airflow điều phối EOD, data test đối soát bắt buộc, staging + approval gate, observability freshness. Mở rộng sang 3–4 báo cáo. Bắt đầu catalog/lineage.
- Run (tháng 10+): blue-green deploy, lineage column-level đầu-cuối, platform self-service cho phòng nghiệp vụ, SLO + on-call, reproducibility đầy đủ (snapshot + versioning quy định). Đo DORA đều đặn.
Mỗi giai đoạn cho giá trị độc lập nên nếu dừng giữa chừng vẫn không lãng phí.
Cạm bẫy & bài học
- Chạy toàn bộ warehouse mỗi lần → chậm, tốn. Dùng slim CI (
state:modified+) và incremental. - Bỏ qua reproducibility từ đầu → tới khi thanh tra hỏi kỳ cũ mới phát hiện không chạy lại được. Gắn tag + snapshot ngay từ giai đoạn crawl.
- Contract giữa domain quá lỏng → một đổi kiểu cột ở silver phá mọi mart phía sau âm thầm. Bật contract sớm.
- Coi self-service là "ai cũng sửa được prod" → guardrail (test bắt buộc, RBAC) phải có trước khi mở quyền.
- Đo tốc độ nhưng quên đo chất lượng → deploy nhanh mà số sai còn tệ hơn chậm. DORA phải đi kèm tỷ lệ test pass.
- Copy PII vào staging cho "giống thật" → vi phạm NĐ13. Luôn ẩn danh non-prod.
Checklist đưa hệ thống báo cáo vào DataOps
- Nguồn vào lakehouse qua CDC; bronze giữ thô để đối chiếu.
- Biến đổi theo medallion trong dbt; mart báo cáo ở tầng gold, có contract.
- DAG EOD chờ sensor dữ liệu đủ; backfill checkout đúng tag kỳ.
- Mọi đổi logic qua PR + maker-checker; không sửa tay prod.
- Data test đối soát bắt buộc (tổng nhóm = tổng danh mục) chạy trong DAG, chặn phục vụ nếu fail.
- SLO freshness + cảnh báo on-call trước giờ đọc báo cáo.
- Lineage đầu-cuối truy được ô báo cáo → bút toán core; audit trail Git gắn mỗi số liệu với commit + PR + người duyệt.
- Reproducibility: tag mỗi kỳ + snapshot nguồn + giữ versioning quy định khi luật đổi.
- Non-prod ẩn danh PII theo NĐ13; môi trường & credential tách biệt.
- Đo DORA + thời gian đóng sổ + tỷ lệ test pass; báo cáo định kỳ cho lãnh đạo.
Use case thực tế
Kỳ nộp báo cáo NPL tháng 6/2026 tại NCB (minh hoạ). 8h05 sáng ngày làm việc đầu tháng, on-call nhận cảnh báo: mart mart_npl_summary chưa sẵn sàng (vi phạm SLO freshness 8h). Truy log DAG eod_regulatory_reporting: chặng dbt test --select tag:report fail vì test đối soát "tổng dư nợ 5 nhóm = tổng danh mục" lệch 1 khoản vay — một khoản tái cơ cấu có số ngày quá hạn NULL nên rơi khỏi mọi nhóm.
Nhờ pipeline có sẵn:
- Lineage chỉ ngay khoản vay đó về bản ghi CDC gốc từ core — 2 phút, không phải dò tay.
- Analyst (maker) mở PR
feat/handle-null-dpdsửa silver để khoảnNULLngày quá hạn xếp vào nhóm theo quy tắc thông tư, thêm một data test chặn tái diễn. - CI chạy 2 phút: slim build 3 model đổi vào schema tạm, 41 test pass gồm test mới.
- Trưởng phòng Rủi ro (checker) đối chiếu diff với thông tư, approve.
- Deploy blue-green, DAG chạy lại, mart NPL xanh lúc 9h20 — báo cáo nộp đúng hạn.
- Kỳ sau thanh tra hỏi về xử lý khoản tái cơ cấu: đội trả lời bằng link PR #518 + lineage, có commit + người duyệt + căn cứ thông tư.
Cùng sự cố này trước dự án sẽ là: phát hiện muộn khi báo cáo đã nộp sai, không biết khoản nào lệch, sửa tay trên prod, không có dấu vết cho thanh tra. Đó là toàn bộ giá trị của DataOps ngân hàng gói trong một buổi sáng.
Ghi nhớ
- Case này ghép cả 8 bài: kiến trúc + medallion + Airflow + CI/CD + quality + observability + platform, phục vụ báo cáo giám sát NHNN.
- Khác biệt cốt lõi của ngân hàng là lớp tuân thủ bao quanh: maker-checker, audit trail, lineage đầu-cuối, reproducibility, NĐ13 — không phải tốc độ.
- Audit trail & lineage để truy ô báo cáo → bút toán core, biến câu trả lời cho thanh tra từ "vài ngày" thành "vài phút" (xem catalog & lineage).
- Reproducibility = tag kỳ + snapshot nguồn + versioning quy định; chạy lại kỳ cũ ra đúng số cũ.
- Data test đối soát (tổng nhóm = tổng danh mục) chạy trong DAG chặn số sai trước khi phục vụ.
- Bảo mật PII theo NĐ13: ẩn danh non-prod, RBAC, môi trường tách biệt (xem privacy & compliance).
- Đo kết quả bằng DORA + thời gian đóng sổ + tỷ lệ test pass; giá trị lớn nhất với lãnh đạo là truy vết.
- Lộ trình crawl → walk → run: mỗi giai đoạn cho giá trị độc lập, cân bằng đổi mới vs kiểm soát.
- Đội mạnh cần cả năng lực kỹ thuật lẫn thói quen vận hành — xem DataOps 8 — team & practice.
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.