Quản trị dữ liệu 3 — Quản lý chất lượng dữ liệu
Vì sao chất lượng dữ liệu là chuyện sống còn
Ở bài tổng quan quản trị chúng ta đã đặt data quality (DQ) làm một trong các trụ cột. Ở bài catalog & lineage ta biết dữ liệu chảy từ đâu đến đâu. Bài này trả lời câu hỏi khó nhất: dữ liệu đó có tin được không?
Trong ngân hàng, dữ liệu sai không chỉ gây khó chịu — nó gây hậu quả pháp lý và tài chính. Một số dư khách hàng lệch, một báo cáo CAR (Capital Adequacy Ratio) nộp NHNN sai, một giao dịch mồ côi không khớp tài khoản: mỗi lỗi có thể thành một finding kiểm toán, một khoản phạt, hoặc mất niềm tin của khách. Chất lượng dữ liệu không phải là "làm sạch một lần" mà là một hệ thống kiểm soát chạy liên tục trong pipeline, tự bắt lỗi trước khi lỗi chảy xuống báo cáo.
Bài này đi từ lý thuyết (6 chiều chất lượng, DQ rule) đến vận hành thực chiến (dbt tests, Great Expectations, đối soát số dư, scorecard, quy trình xử lý sự cố), và có 4 câu SQL kiểm tra chất lượng chạy được ngay trên sandbox PostgreSQL của chúng ta.
Sáu chiều chất lượng dữ liệu
Ngành dữ liệu quy ước sáu dimension (chiều) để nói về chất lượng. Đo được từng chiều mới quản được, nên với mỗi chiều ta gắn một cách đo cụ thể trên dữ liệu ngân hàng.
| Chiều | Định nghĩa | Ví dụ ngân hàng | Cách đo |
|---|---|---|---|
| Completeness (đầy đủ) | Trường bắt buộc không bị thiếu/NULL | customers.city trống → không phân khúc được theo vùng; CIF thiếu ngày sinh → không tính tuổi rủi ro | Tỷ lệ NULL trên tổng số dòng |
| Accuracy (chính xác) | Giá trị phản ánh đúng thực tế | Số dư trong DW khớp core banking; lãi suất khớp hợp đồng | Đối soát với nguồn tin cậy (system of record) |
| Consistency (nhất quán) | Cùng một thực thể có giá trị thống nhất giữa các hệ | Tổng số dư tài khoản của khách = số hiển thị ở internet banking | So khớp chéo giữa các bảng/hệ thống |
| Timeliness (kịp thời) | Dữ liệu sẵn sàng đúng lúc cần | Bảng giao dịch cuối ngày phải nạp xong trước 6h sáng để chạy báo cáo | Độ trễ giữa created_at và thời điểm nạp |
| Validity (hợp lệ) | Giá trị nằm trong miền/định dạng cho phép | balance không âm với tài khoản tiết kiệm; currency thuộc {VND, USD, EUR}; amount giao dịch ≠ 0 | Kiểm tra domain/format/range |
| Uniqueness (duy nhất) | Không trùng lặp bản ghi/khóa | account_no là duy nhất; không có 2 CIF cho cùng một người | So COUNT(*) với COUNT(DISTINCT khóa) |
Một lỗi thực tế thường vi phạm nhiều chiều cùng lúc. Ví dụ CDC (Change Data Capture) chạy hai lần vì retry sẽ sinh giao dịch trùng (vi phạm uniqueness) và làm tổng số dư sai (vi phạm accuracy + consistency). Vì vậy khi thiết kế kiểm tra, ta ánh xạ từng chiều thành một hoặc nhiều DQ rule cụ thể.
DQ rule và ngưỡng
Một DQ rule là một khẳng định kiểm chứng được về dữ liệu, kèm ngưỡng (threshold) phân biệt "chấp nhận được" với "sự cố". Cấu trúc chuẩn của một rule:
- Đối tượng: bảng/cột nào (
accounts.balance). - Assertion: điều gì phải đúng (
balance >= 0với tài khoản không thấu chi). - Chiều: rule này đo chiều nào (validity).
- Ngưỡng: bao nhiêu vi phạm thì fail. Có hai kiểu:
- Hard rule (block): 0 vi phạm mới pass — dùng cho ràng buộc tuyệt đối (khóa chính duy nhất, khóa ngoại toàn vẹn). Fail thì chặn pipeline.
- Soft rule (warn): cho phép tỷ lệ nhỏ — ví dụ "≤ 0.5% khách thiếu
city". Fail thì cảnh báo nhưng vẫn chạy tiếp.
- Severity:
error/warn— quyết định hành vi khi vi phạm.
Ngưỡng nên xuất phát từ nghiệp vụ, không đặt tùy hứng. Ví dụ ngưỡng completeness cho city là 0.5% vì đó là mức "nhiễu" lịch sử ta chấp nhận; nếu vọt lên 5% nghĩa là form nhập liệu hoặc job ETL vừa hỏng. Ngưỡng cũng nên có baseline động: đối chiếu với trung bình 7 ngày để bắt bất thường tương đối, không chỉ tuyệt đối.
Vòng đời quản lý chất lượng dữ liệu
Vòng lặp này không có điểm kết thúc: mỗi sự cố lại tinh chỉnh rule và ngưỡng. Ta đi qua từng khối.
Data profiling — khám phá trước khi ra rule
Profiling là bước chụp ảnh thống kê dữ liệu để hiểu hình dạng thật của nó trước khi viết rule. Không profile mà ra rule là đoán mò. Với mỗi cột ta muốn biết: số dòng, tỷ lệ NULL, số giá trị phân biệt (cardinality), min/max, top giá trị, mẫu định dạng.
Trên sandbox, một câu profiling nhanh cho accounts cho ta bức tranh phân bố tiền tệ và biên số dư:
-- ▶ Chạy được
SELECT
currency,
COUNT(*) AS so_tk,
MIN(balance) AS balance_min,
MAX(balance) AS balance_max,
ROUND(AVG(balance), 2) AS balance_avg,
COUNT(*) FILTER (WHERE balance < 0) AS so_am
FROM accounts
GROUP BY currency
ORDER BY so_tk DESC;
Nhìn kết quả, nếu cột so_am > 0 ở loại tiền không cho thấu chi, ta có ngay ứng viên cho một validity rule. Nếu một currency chỉ xuất hiện vài dòng, có thể đó là giá trị rác cần kiểm tra. Profiling định kỳ (hàng tuần) còn giúp phát hiện data drift — phân bố lệch dần khỏi baseline.
Kiểm thử chất lượng: đo từng chiều bằng SQL
Bốn câu dưới đây minh họa cách biến bốn chiều thành truy vấn chạy được ngay. Mỗi câu trả về "bằng chứng vi phạm" hoặc "chỉ số" — đây chính là nền tảng mà dbt tests và Great Expectations gói lại phía trên.
Completeness — đếm và tính tỷ lệ NULL của customers.city. Đây là cột hay thiếu do nhập liệu chi nhánh không đồng bộ:
-- ▶ Chạy được
SELECT
COUNT(*) AS tong_khach,
COUNT(*) FILTER (WHERE city IS NULL OR city = '') AS thieu_city,
ROUND(100.0 * COUNT(*) FILTER (WHERE city IS NULL OR city = '') / COUNT(*), 2) AS ty_le_thieu_pct
FROM customers;
Uniqueness — account_no phải là duy nhất tuyệt đối. Nếu COUNT(*) ≠ COUNT(DISTINCT account_no) là có trùng, một hard rule kinh điển:
-- ▶ Chạy được
SELECT
COUNT(*) AS tong_dong,
COUNT(DISTINCT account_no) AS so_account_no_phan_biet,
COUNT(*) - COUNT(DISTINCT account_no) AS so_ban_ghi_trung
FROM accounts;
Validity — bắt các giao dịch bất hợp lệ: amount = 0 (giao dịch rỗng vô nghĩa) hoặc số dư âm ở tài khoản. Câu này gộp hai kiểm tra validity, trả về số dòng vi phạm từng loại:
-- ▶ Chạy được
SELECT
COUNT(*) FILTER (WHERE amount = 0) AS giao_dich_amount_0,
COUNT(*) FILTER (WHERE amount < 0) AS giao_dich_amount_am,
MIN(amount) AS amount_min,
MAX(amount) AS amount_max
FROM transactions;
Referential integrity / consistency — bắt giao dịch mồ côi (orphan): transactions.account_id không khớp accounts.id. Đây là lỗi cực nguy hiểm vì giao dịch tồn tại nhưng không quy được về tài khoản nào, làm sai tổng số dư. Dùng LEFT JOIN ... WHERE a.id IS NULL:
-- ▶ Chạy được
SELECT COUNT(*) AS so_giao_dich_mo_coi
FROM transactions t
LEFT JOIN accounts a ON t.account_id = a.id
WHERE a.id IS NULL;
Kết quả > 0 nghĩa là toàn vẹn tham chiếu bị vỡ — thường do thứ tự nạp bảng sai (nạp transactions trước accounts) hoặc do bản ghi accounts bị xóa mà giao dịch chưa xử lý.
Đưa test vào pipeline: dbt tests và Great Expectations
Chạy SQL tay chỉ để khám phá. Ở production, kiểm tra phải tự động chạy mỗi lần build và chặn dữ liệu bẩn trước khi publish. Hai công cụ phổ biến nhất:
dbt tests
Nếu dùng dbt (xem dbt tổng quan), test khai báo bằng YAML gắn vào cột. Bản chất mỗi test là một câu SQL trả về dòng vi phạm — 0 dòng là pass. Đây chính là các chiều ở trên được đóng gói:
# models/marts/_marts.yml (minh hoạ dbt 1.x)
version: 2
models:
- name: dim_accounts
columns:
- name: account_no
data_tests:
- unique # uniqueness
- not_null # completeness
- name: customer_id
data_tests:
- relationships: # referential integrity
arguments:
to: ref('dim_customers')
field: customer_id
- name: balance
data_tests:
- dbt_utils.accepted_range: # validity
arguments:
min_value: 0
dbt còn có singular test (một file .sql tùy ý trong tests/) cho logic phức tạp như đối soát, và cấu hình severity: warn / error_if / warn_if để đặt ngưỡng soft/hard. Chi tiết trong dbt tests & docs.
Great Expectations (GX)
Great Expectations là framework Python định nghĩa các "expectation" — kỳ vọng về dữ liệu — rồi validate và sinh Data Docs (báo cáo HTML). Nó mạnh khi dữ liệu chưa vào dbt (validate ở tầng landing, hoặc validate file/DataFrame). Ví dụ minh họa:
# minh hoạ Great Expectations
import great_expectations as gx
batch = context.get_batch(...) # accounts
batch.expect_column_values_to_not_be_null("account_no") # completeness
batch.expect_column_values_to_be_unique("account_no") # uniqueness
batch.expect_column_values_to_be_in_set("currency", ["VND","USD","EUR"]) # validity
batch.expect_column_values_to_be_between("balance", min_value=0, mostly=0.999) # validity, ngưỡng soft 99.9%
Tham số mostly=0.999 chính là ngưỡng soft: cho phép tối đa 0.1% vi phạm. GX chạy được như một task trong Airflow, fail thì đẩy alert. So sánh nhanh:
| Tiêu chí | dbt tests | Great Expectations |
|---|---|---|
| Vị trí | Bên trong dbt build (trên DW) | Bất kỳ đâu (landing, file, DataFrame, DW) |
| Ngôn ngữ | YAML + SQL | Python |
| Đầu ra | Log build, exit code | Data Docs (HTML) + validation result |
| Điểm mạnh | Gắn chặt lineage, dễ với data team SQL | Profiling tự động, kiểm tra ngoài DW, báo cáo đẹp |
Trong thực tế nhiều ngân hàng dùng cả hai: GX validate ở cửa ngõ nạp (raw layer), dbt tests validate ở tầng transform (mart).
Reconciliation — đối soát số dư nguồn và DW
Đối soát (reconciliation) là kiểm tra accuracy + consistency ở cấp cao nhất: con số trong DW có khớp hệ nguồn không? Với ngân hàng, đối soát số dư là nghiệp vụ bắt buộc chạy cuối ngày (EOD).
Nguyên tắc: chọn một control total — thường là tổng số dư, tổng số giao dịch, hoặc số bản ghi — tính ở cả hai phía và so sánh, cho phép sai lệch bằng 0 (với tiền tệ) hoặc trong ngưỡng làm tròn.
Trên sandbox, control total phía DW (giả định accounts chính là bảng trong DW) tính như sau — con số này sẽ được so với con số cùng loại xuất từ core banking:
-- ▶ Chạy được
SELECT
currency,
COUNT(*) AS so_tai_khoan,
SUM(balance) AS tong_so_du
FROM accounts
GROUP BY currency
ORDER BY currency;
Một tinh chỉnh phổ biến: đối soát movement thay vì snapshot — tổng biến động giao dịch trong ngày phải bằng chênh lệch số dư đầu–cuối ngày. Nếu không khớp, ta có một "break" cần điều tra: giao dịch bị thiếu, bị trùng, hoặc timing khác nhau giữa hai hệ.
DQ scorecard và dashboard
Kết quả test rời rạc thì khó quản trị. Ta gom chúng thành DQ scorecard — một điểm số tổng hợp theo dataset/domain, cập nhật mỗi lần chạy. Mô hình đơn giản:
- Mỗi rule cho 1 điểm pass/fail (hoặc tỷ lệ pass với soft rule).
- Điểm dataset = trung bình có trọng số các rule (hard rule trọng số cao hơn).
- Điểm domain (VD "Khách hàng", "Tài khoản", "Giao dịch") = tổng hợp từ các dataset.
Ví dụ scorecard minh họa cho domain Tài khoản:
| Rule | Chiều | Ngưỡng | Kết quả | Điểm |
|---|---|---|---|---|
account_no unique | Uniqueness | 0 trùng | 0 trùng | 100% |
account_no not null | Completeness | 0 NULL | 0 NULL | 100% |
balance >= 0 | Validity | 0 vi phạm | 3 vi phạm | Fail (hard) |
customer_id khớp customers | Integrity | 0 orphan | 0 orphan | 100% |
| Recon số dư VND | Accuracy | lệch = 0 | khớp | 100% |
| Điểm domain | 80% (Cảnh báo) |
Scorecard được đẩy lên dashboard (Grafana, Metabase, hoặc bảng riêng trong DW) với các panel: điểm theo thời gian, top rule fail, số incident mở. Dashboard này là bằng chứng cho kiểm toán rằng DQ được kiểm soát liên tục — liên hệ thêm bài whitelist governance & quality.
Data incident: quy trình xử lý sự cố chất lượng
Khi một hard rule fail hoặc scorecard tụt, đó là một data incident cần xử lý có kỷ luật như sự cố hệ thống. Quy trình khuyến nghị:
- Detect — test/monitor phát hiện, tự tạo alert (Slack/PagerDuty) kèm ngữ cảnh (rule nào, bảng nào, số dòng vi phạm).
- Triage — phân loại severity: P1 (số liệu tài chính/báo cáo NHNN sai) chặn ngay; P3 (một cột phụ thiếu) xử lý sau.
- Contain — chặn dữ liệu bẩn lan xuống. Với dbt, test fail dừng build nên mart giữ phiên bản tốt cuối cùng; hạ cờ "reconciled".
- Root cause — dùng lineage truy ngược nguồn: đổi schema upstream? CDC chạy hai lần? Job nạp sai thứ tự?
- Remediate — sửa dữ liệu (backfill), sửa pipeline, và thêm/siết rule để lỗi không tái diễn.
- Post-mortem — ghi lại nguyên nhân, tác động, hành động phòng ngừa. Không đổ lỗi, tập trung vào hệ thống.
Mấu chốt là thời gian phát hiện (time-to-detect) phải ngắn hơn thời gian dữ liệu chảy tới người dùng cuối. Test trong pipeline giúp đạt điều đó.
Data contract — chặn lỗi từ gốc
Nhiều incident sinh ra vì upstream đổi mà không báo. Data contract là thỏa thuận hình thức giữa bên sản xuất dữ liệu (producer, ví dụ team core banking) và bên tiêu thụ (consumer, ví dụ team DW), quy định:
- Schema: tên cột, kiểu dữ liệu, cột bắt buộc.
- Semantics: ý nghĩa từng trường, đơn vị (
balancelà VND hay đơn vị nghìn?). - Quality guarantees:
account_noduy nhất và không NULL;currencythuộc tập cho phép. - SLA: dữ liệu sẵn sàng lúc mấy giờ (timeliness), tần suất cập nhật.
- Versioning & thay đổi: breaking change phải báo trước N ngày.
Contract được viết dưới dạng file (YAML/JSON) và được kiểm tra tự động: mỗi lần producer publish, một validator (GX hoặc schema check) đối chiếu dữ liệu với contract; vi phạm thì chặn ngay tại nguồn. Điều này dịch chuyển việc bắt lỗi từ "consumer phát hiện muộn" sang "producer bị chặn sớm" — rẻ và an toàn hơn nhiều.
Use case thực tế
Bối cảnh: Đội DW của NCB chạy job EOD nạp accounts và transactions từ core banking. Sáng thứ Hai, dashboard báo cáo tiền gửi hiển thị tổng số dư VND cao hơn 4.2 tỷ so với con số core banking gửi bộ phận kế toán.
Diễn biến xử lý theo quy trình incident:
- Detect (07:12): Recon job đêm đã fail rule "Recon số dư VND lệch = 0", chênh lệch 4,200,000,000 VND. Alert vào kênh
#data-incident, severity P1 vì đụng số liệu tài chính. - Triage: Trưởng nhóm DQ xác nhận P1, hạ cờ
reconciledcủa mart tiền gửi, thông báo BI không dùng số của sáng nay. - Contain: dbt build đêm thực ra đã fail ở test
account_no uniquenhưng cấu hìnhseverity: warnnên không chặn — đây là lỗ hổng. Mart vẫn publish. - Root cause: chạy tay câu uniqueness (như block SQL ở trên) phát hiện 312 bản ghi
account_notrùng. Dùng lineage truy ngược: CDC bị retry sau một lần timeout mạng, nạp trùng một batch. Số dư của 312 tài khoản bị cộng đôi → 4.2 tỷ. - Remediate: xóa batch trùng theo
idmới nhất, chạy lại recon → khớp. Đổi testaccount_no uniquetừwarnsangerror(hard rule). Thêm rule idempotency cho CDC. - Post-mortem: nguyên nhân gốc là hard rule bị hạ nhầm thành warn + CDC không idempotent. Hành động: audit toàn bộ severity của rule tài chính, bổ sung data contract với team core yêu cầu mỗi batch có
batch_idđể dedupe.
Kết quả: time-to-detect đã tốt (recon bắt được trong đêm) nhưng contain thất bại do cấu hình ngưỡng sai. Bài học: rule đúng nhưng ngưỡng sai cũng vô dụng — ngưỡng phải phản ánh mức nghiêm trọng nghiệp vụ.
Ghi nhớ
- Sáu chiều chất lượng: completeness, accuracy, consistency, timeliness, validity, uniqueness — mỗi chiều phải ánh xạ thành DQ rule đo được.
- DQ rule = đối tượng + assertion + chiều + ngưỡng + severity. Phân biệt hard rule (0 vi phạm, chặn pipeline) và soft rule (ngưỡng %, chỉ cảnh báo).
- Profiling trước, ra rule sau — đừng đoán ngưỡng; lấy từ nghiệp vụ và baseline lịch sử.
- Tự động hóa test trong pipeline: dbt tests (YAML+SQL, trên DW) và Great Expectations (Python, validate ở nhiều tầng, sinh Data Docs). Nhiều nơi dùng cả hai.
- Reconciliation đối soát control total (tổng số dư, số giao dịch) giữa core banking và DW — với tiền tệ, ngưỡng lệch phải bằng 0.
- DQ scorecard/dashboard biến kết quả test rời rạc thành điểm số theo domain, là bằng chứng kiểm soát cho kiểm toán.
- Data incident process: Detect → Triage → Contain → Root cause → Remediate → Post-mortem; đo bằng time-to-detect.
- Data contract dịch việc bắt lỗi về phía producer: khai báo schema, semantics, quality, SLA, versioning và kiểm tra tự động ngay tại nguồn.
- SQL kiểm tra chất lượng cốt lõi: đếm NULL (completeness),
COUNT(*)vsCOUNT(DISTINCT)(uniqueness), lọc domain/range (validity),LEFT JOIN ... WHERE IS NULLbắt orphan (integrity).
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.