BI mã nguồn mở 6 — Lớp ngữ nghĩa & chỉ số nhất quán
Vấn đề: "mỗi báo cáo một con số"
Trong một cuộc họp giao ban của NCB, phòng Kế hoạch báo cáo tỷ lệ CASA (Current Account Savings Account — tỷ trọng tiền gửi không kỳ hạn) quý này là 22,4%. Phòng Nguồn vốn cầm một dashboard khác nói 20,1%. Bộ phận số hoá đưa ra con số 23,0% từ Superset. Ba con số, một chỉ tiêu — và không ai sai hoàn toàn, vì mỗi bên định nghĩa CASA hơi khác: có bên tính trên số dư cuối kỳ, có bên trên số dư bình quân; có bên đưa cả tiền gửi thanh toán của tổ chức, có bên chỉ tính cá nhân; có bên gồm ngoại tệ quy đổi, có bên chỉ VND.
Đây là căn bệnh kinh điển của mọi hệ thống BI khi trưởng thành: logic tính toán bị nhân bản và trôi dạt (metric drift). Mỗi khi một analyst cần một chỉ tiêu, họ viết lại công thức trong SQL của dashboard mình. Vài chục dashboard sau, cùng một cái tên "tỷ lệ nợ xấu" tồn tại dưới hàng chục biến thể SQL khác nhau. Hệ quả không chỉ là số liệu lệch nhau, mà là mất niềm tin vào dữ liệu — thứ tài sản khó xây, dễ mất nhất của một đội dữ liệu.
Bài này bàn về giải pháp gốc rễ: định nghĩa chỉ số một lần, ở một nơi, rồi tái sử dụng khắp nơi — thông qua khái niệm lớp ngữ nghĩa (semantic layer) và metrics layer. Ở loạt bài này ta đã đi qua Metabase và Superset; giờ ta xem cả hai công cụ chống metric drift thế nào, và khi nào cần một lớp cao hơn.
Lớp ngữ nghĩa (semantic layer) là gì?
Semantic layer là một tầng trừu tượng nằm giữa warehouse (dữ liệu thô) và các công cụ BI. Nhiệm vụ của nó là dịch những bảng/cột kỹ thuật (accounts.balance, transactions.kind) thành ngôn ngữ nghiệp vụ mà người dùng hiểu ("số dư bình quân", "tỷ lệ CASA", "dư nợ tín dụng"), đồng thời giữ định nghĩa của các khái niệm đó ở một nơi duy nhất (single source of definition).
Nó gồm ba thành phần cốt lõi:
- Metric (chỉ số/độ đo): một đại lượng có thể tổng hợp (aggregate) — ví dụ
Số dư bình quân = AVG(balance),Tỷ lệ nợ xấu = dư nợ nhóm 3-5 / tổng dư nợ. Metric mang theo hàm tổng hợp và công thức, không phải một giá trị cố định. - Dimension (chiều): trục để cắt lát metric — chi nhánh, loại tiền tệ (currency), thời gian, phân khúc khách hàng. Cùng một metric "số dư bình quân" nhìn theo
currencyhay theo tháng là hai câu chuyện khác nhau. - Relationship (mối quan hệ join): khai báo sẵn cách các bảng nối với nhau (
transactions.account_id → accounts.id → customers.id), để người dùng không phải tự viết JOIN mỗi lần.
Khi ba thứ này được khai báo tập trung, mọi công cụ BI truy vấn qua lớp ngữ nghĩa đều nhận về cùng một con số cho cùng một chỉ tiêu. Đó là điểm mấu chốt.
Cần phân biệt hai mức độ:
- Semantic layer trong công cụ BI: mỗi công cụ (Metabase, Superset) có cơ chế riêng để định nghĩa metric/model — nhưng định nghĩa đó chỉ dùng được trong nội bộ công cụ đó.
- Headless BI / metrics layer độc lập: một lớp riêng biệt (dbt Semantic Layer, Cube), nằm ngoài công cụ BI, phục vụ nhiều công cụ cùng lúc. "Headless" nghĩa là nó không có giao diện trực quan hoá riêng — chỉ cung cấp định nghĩa và API trả metric.
Cách Metabase tiếp cận: Models + Metrics
Metabase chống metric drift bằng hai lớp: Models và Metrics.
Model là một câu hỏi được "phong" thành nguồn dữ liệu tái sử dụng (một virtual table sạch). Thay vì để analyst join transactions với accounts với customers mỗi lần, đội dữ liệu tạo sẵn một model giao_dich_lam_giau đã join sẵn, đổi tên cột sang tiếng nghiệp vụ, gán kiểu ngữ nghĩa (semantic type — ví dụ đánh dấu cột là "tiền tệ", "thành phố"). Mọi câu hỏi sau xây trên model này, thừa hưởng logic join và làm sạch — không lặp lại.
Metric (từ Metabase 50 trở đi, tính năng metric được làm mới) là một định nghĩa chỉ số tập trung: bạn khai báo một lần "Số dư bình quân theo loại tiền" gồm hàm tổng hợp + bộ lọc + nhóm, đặt tên nghiệp vụ, rồi cả tổ chức chọn metric đó từ danh mục thay vì tự tính lại. Khi định nghĩa nợ xấu thay đổi (ví dụ bổ sung nhóm 2 vào tử số), sửa một chỗ — mọi dashboard dùng metric đó cập nhật theo.
Điểm mạnh của Metabase là thân thiện với người không rành SQL: định nghĩa metric qua giao diện query builder. Điểm yếu là định nghĩa nằm trong metadata của Metabase, khó chia sẻ sang công cụ khác và khó đưa vào quy trình version control (Git) một cách tự nhiên.
Cách Superset tiếp cận: Dataset metrics & Calculated columns
Superset lấy dataset làm trung tâm (xem bài Superset căn bản). Mỗi dataset — dù là physical (bảng thật) hay virtual (một câu SQL) — là nơi khai báo:
- Metrics: biểu thức SQL tổng hợp, ví dụ
AVG(balance)đặt tênso_du_binh_quan, haySUM(CASE WHEN kind='transfer' THEN 1 ELSE 0 END). Metric định nghĩa ở tầng dataset dùng lại được cho mọi chart dựng trên dataset đó. - Calculated columns: cột phái sinh cấp dòng (row-level), ví dụ
balance / 24000để quy USD ra VND, hay trích tháng từcreated_at. Đây là chiều/thuộc tính, không phải aggregate.
Vì Superset khai báo metric bằng SQL trực tiếp, nó mạnh và linh hoạt cho đội DE/DA nhưng đòi hỏi biết SQL. Định nghĩa cũng nằm trong metadata database của Superset. Superset hỗ trợ export/import dataset dưới dạng YAML, nên có thể đưa định nghĩa vào Git — một bước tiến về mặt quản trị so với việc chỉ click chuột.
So sánh nhanh
| Tiêu chí | Metabase | Superset |
|---|---|---|
| Đơn vị tái sử dụng | Model + Metric | Dataset (physical/virtual) |
| Cách định nghĩa metric | Query builder (ít cần SQL) | Biểu thức SQL |
| Đối tượng chính | Nghiệp vụ, self-service | DA/DE rành SQL |
| Version control | Khó (nằm trong app DB) | Khá (export YAML → Git) |
| Phạm vi định nghĩa | Chỉ trong Metabase | Chỉ trong Superset |
Điểm chung — và cũng là giới hạn — của cả hai: định nghĩa metric bị khoá trong công cụ. Nếu NCB dùng cả Metabase (cho nghiệp vụ) lẫn Superset (cho DA), một chỉ tiêu như CASA vẫn phải định nghĩa hai lần. Đây chính là lý do metrics layer độc lập ra đời.
Xu hướng metrics layer / headless BI
Ý tưởng: tách định nghĩa metric ra khỏi công cụ BI, đặt nó thành một lớp riêng mà mọi công cụ (Metabase, Superset, Excel, notebook Python) đều gọi qua API. Định nghĩa sống trong code, review qua pull request, versioned bằng Git. Đây gọi là headless BI hoặc metrics layer.
- dbt Semantic Layer (MetricFlow): dbt vốn là công cụ transform trong warehouse (xem dbt tổng quan). dbt Semantic Layer cho phép khai báo metric ngay trong dbt project bằng YAML — dựa trên các model đã build. MetricFlow là engine sinh SQL đúng cho từng truy vấn metric, tự xử lý join và mức độ tổng hợp. Ưu điểm: định nghĩa metric nằm cùng repo với transform, cùng quy trình CI/CD, cùng version control. Tài liệu: https://docs.getdbt.com/docs/build/about-metricflow
- Cube (cube.dev): một metrics layer/headless BI độc lập, khai báo metric và dimension trong "data models", phục vụ qua REST/GraphQL/SQL API, có sẵn caching mạnh. Không gắn với một công cụ transform cụ thể.
Với những công cụ này, metric được định nghĩa một lần duy nhất; mọi BI tool trở thành "màn hình hiển thị" của cùng một nguồn định nghĩa. Đó là lời giải triệt để cho "mỗi báo cáo một con số".
Lưu ý thực tế: mức độ tích hợp giữa Metabase/Superset với dbt Semantic Layer đang tiến triển và không đồng đều — cần kiểm tra phiên bản cụ thể khi triển khai. Nói cách khác, đây là hướng đi đúng nhưng chưa phải "cắm là chạy" cho mọi tổ hợp công cụ.
Định nghĩa chuẩn các chỉ số ngân hàng (định nghĩa một lần)
Trước khi khai báo trong bất kỳ công cụ nào, đội dữ liệu và nghiệp vụ NCB cần thống nhất công thức bằng văn bản trong một glossary (từ điển chỉ số). Dưới đây là bộ chỉ tiêu lõi — công thức mang tính minh hoạ nguyên tắc, con số ngưỡng phải bám quy định NHNN và chính sách nội bộ:
| Chỉ tiêu | Định nghĩa (rút gọn) | Điểm dễ trôi dạt |
|---|---|---|
| Số dư bình quân | Trung bình số dư theo thời gian (thường bình quân ngày trong kỳ), không phải số dư cuối kỳ | Bình quân ngày vs. cuối kỳ; gồm/không gồm tài khoản đóng |
| CASA | Số dư tiền gửi không kỳ hạn / tổng tiền gửi huy động | Cuối kỳ vs. bình quân; cá nhân vs. gồm tổ chức; quy đổi ngoại tệ |
| NIM (Net Interest Margin) | Thu nhập lãi thuần / tài sản sinh lãi bình quân | Mẫu số dùng tổng tài sản hay chỉ tài sản sinh lãi |
| Tỷ lệ nợ xấu (NPL) | Dư nợ nhóm 3-5 / tổng dư nợ | Có gồm nợ nhóm 2 (cần chú ý) không; theo TT 11/2021 |
| Tăng trưởng tín dụng | (Dư nợ kỳ này − đầu năm) / dư nợ đầu năm | So với đầu năm vs. cùng kỳ năm trước (YoY) |
Nguyên tắc vàng: mỗi ô "điểm dễ trôi dạt" ở trên phải được chốt một lần trong glossary, rồi mã hoá đúng như thế vào lớp ngữ nghĩa. Khi CASA đã chốt là "bình quân ngày, gồm cả tổ chức, quy đổi về VND", thì định nghĩa metric phải phản ánh chính xác điều đó — và không ai được viết lại công thức khác trong dashboard riêng.
Minh hoạ metric bằng SQL trên sandbox
Sandbox chỉ có schema đơn giản (accounts, transactions, customers...), không có bảng phân loại nợ, nên ta minh hoạ nguyên tắc định nghĩa metric — cách một "metric" được diễn đạt thành một biểu thức tổng hợp trên dimension.
Metric số dư bình quân theo loại tiền tệ (dimension = currency). Đây đúng là dạng biểu thức bạn sẽ dán vào ô "SQL expression" của một dataset metric trong Superset:
-- ▶ Chạy được
SELECT
currency,
COUNT(*) AS so_tai_khoan,
ROUND(AVG(balance)::numeric, 2) AS so_du_binh_quan,
ROUND(SUM(balance)::numeric, 2) AS tong_so_du
FROM accounts
GROUP BY currency
ORDER BY tong_so_du DESC;
Metric tỷ lệ giao dịch loại "transfer" trên tổng số giao dịch — dạng tỷ lệ (ratio) là kiểu metric hay bị mỗi người tính một khác. Chốt công thức một lần và tái dùng:
-- ▶ Chạy được
SELECT
ROUND(
100.0 * SUM(CASE WHEN kind = 'transfer' THEN 1 ELSE 0 END)
/ NULLIF(COUNT(*), 0)
::numeric, 2) AS ty_le_transfer_pct,
COUNT(*) AS tong_giao_dich
FROM transactions;
Hai biểu thức AVG(balance) và 100.0 * SUM(CASE...) / NULLIF(COUNT(*),0) chính là thân của một metric. Đưa nó vào một chỗ (dataset metric của Superset, Metric của Metabase, hoặc YAML của dbt Semantic Layer) là ta đã "đóng gói định nghĩa". Chú ý NULLIF(...,0) để tránh chia cho 0 — một loại lỗi hay lặp lại nếu ai cũng tự viết công thức.
Caching & tăng tốc
Lớp ngữ nghĩa không chỉ để nhất quán, nó còn là điểm đặt caching hợp lý. Có ba mức tăng tốc, tách bạch:
- Query cache (cache kết quả truy vấn): cả Metabase và Superset đều cache kết quả một truy vấn trong một TTL (time-to-live). Mở lại cùng chart trong khoảng TTL → trả từ cache (thường Redis), không hỏi lại warehouse. Giảm tải DB gốc, tăng tốc dashboard. Phù hợp với dashboard xem lặp lại nhiều lần trong ngày.
- Kết quả tính sẵn trong metrics layer: Cube có lớp pre-aggregation — tính sẵn và lưu các tổ hợp metric × dimension phổ biến, để truy vấn trả về gần như tức thì. Đây là cache ở tầng ngữ nghĩa, không phải tầng công cụ BI.
- Materialization ở warehouse/dbt: thay vì cache tạm, ta vật chất hoá (materialize) kết quả thành bảng thật trong warehouse bằng dbt (
materialized: tablehoặcincremental). Ví dụ một bảngcasa_dailytổng hợp CASA theo ngày × chi nhánh, dbt build hằng đêm. Dashboard đọc bảng nhỏ này thay vì quéttransactionstriệu dòng.
| Mức | Nơi lưu | Độ tươi | Dùng khi |
|---|---|---|---|
| Query cache | Redis (BI tool) | Cũ trong TTL | Dashboard xem lặp lại, chấp nhận trễ vài phút |
| Pre-aggregation | Metrics layer (Cube) | Refresh theo lịch | Cần trả metric nhanh cho nhiều tool |
| Materialization | Bảng trong warehouse | Refresh theo batch dbt | Bảng tổng hợp nặng, cần ổn định & tái dùng SQL |
Nguyên tắc chọn: đẩy phần tính nặng và ổn định xuống warehouse (materialization/dbt), để cache của BI tool chỉ lo phần trình bày và tương tác. Đừng dùng query cache để che giấu một truy vấn thiết kế tồi — nó chỉ trì hoãn cơn đau.
Quản trị định nghĩa chỉ số
Định nghĩa metric là tài sản — phải quản trị như code (xem thêm governance của loạt này và kiểm soát truy cập):
- Versioning: đặt định nghĩa metric trong Git khi có thể (YAML của Superset dataset, YAML của dbt Semantic Layer, model Cube). Mỗi thay đổi công thức đều có lịch sử — biết ai đổi CASA, đổi khi nào, vì sao.
- Review: thay đổi một metric lõi (nợ xấu, NIM) phải qua pull request có người nghiệp vụ duyệt, không để một analyst tự ý sửa. Một dấu phẩy trong công thức nợ xấu có thể làm lệch báo cáo gửi NHNN.
- Glossary (từ điển chỉ số): một tài liệu sống, ánh xạ tên nghiệp vụ ↔ công thức ↔ nguồn dữ liệu ↔ người sở hữu (owner). Đây là "hợp đồng" giữa nghiệp vụ và kỹ thuật. Gắn với chất lượng dữ liệu và KPI/metrics.
- Tránh trùng lặp logic: quy tắc "không viết lại công thức đã có trong danh mục metric". Nếu chỉ tiêu chưa có, bổ sung vào metrics layer để lần sau tái dùng — không copy-paste SQL sang dashboard mới.
Về mô hình dữ liệu bên dưới: một lược đồ chiều rõ ràng (star schema, mô hình Kimball) làm metrics layer gọn hơn nhiều — fact table chứa số đo, dimension table chứa chiều, join keys sạch. Semantic layer đẹp nhất khi ngồi trên một mô hình chiều tử tế.
Use case thực tế
Bối cảnh: NCB dùng Metabase cho phòng nghiệp vụ và Superset cho đội DA. Sau kiểm toán nội bộ, phát hiện tỷ lệ CASA trên 3 dashboard lệch nhau (22,4% / 20,1% / 23,0%) do mỗi nơi tự viết công thức. Ban lãnh đạo yêu cầu "một con số CASA duy nhất".
Cách làm — chuẩn hoá qua lớp ngữ nghĩa:
- Chốt định nghĩa (tuần 1): đội dữ liệu + Nguồn vốn họp, thống nhất glossary: CASA = số dư bình quân ngày tiền gửi không kỳ hạn (cá nhân + tổ chức) / tổng số dư bình quân ngày tiền gửi huy động, quy đổi về VND, loại tài khoản đã đóng. Ghi vào glossary có owner là Trưởng phòng Nguồn vốn.
- Mã hoá một lần (tuần 2): khai báo metric
casa_ratiotrong dbt Semantic Layer (YAML, versioned trong Git), dựa trên bảng tổng hợpdeposit_dailymà dbt materialize hằng đêm. Đưa qua review: 1 người kỹ thuật + 1 người nghiệp vụ duyệt PR. - Kết nối công cụ (tuần 3): Superset và Metabase cùng đọc metric
casa_ratio; các dashboard cũ chuyển sang gọi metric chung, gỡ bỏ công thức tự viết. - Tăng tốc: query cache TTL 30 phút cho các dashboard xem lặp; bảng
deposit_daily(materialization) chịu phần quét nặng thay vìtransactionsgốc.
Kết quả: ba dashboard cho cùng một con số (ví dụ 21,7%); thời gian load dashboard CASA giảm từ ~18 giây (quét trực tiếp) xuống dưới 2 giây (đọc bảng tổng hợp + cache); mỗi lần điều chỉnh định nghĩa chỉ sửa một file YAML, có lịch sử Git và người duyệt. Metric drift được chặn từ gốc.
Ghi nhớ
- "Mỗi báo cáo một con số" sinh ra do logic tính toán bị nhân bản và trôi dạt (metric drift) qua từng dashboard — hệ quả nặng nhất là mất niềm tin vào dữ liệu.
- Semantic layer nằm giữa warehouse và các công cụ BI, gồm 3 phần: metric (độ đo + hàm tổng hợp), dimension (chiều cắt lát), relationship (join khai báo sẵn). Mục tiêu: single source of definition.
- Metabase dùng Models + Metrics (query builder, thân thiện nghiệp vụ); Superset dùng dataset metrics + calculated columns (SQL, mạnh cho DA/DE). Cả hai đều khoá định nghĩa trong nội bộ công cụ.
- Metrics layer / headless BI (dbt Semantic Layer/MetricFlow, Cube) tách định nghĩa ra ngoài công cụ, sống trong code + Git, phục vụ nhiều tool cùng một định nghĩa.
- Chuẩn hoá chỉ tiêu ngân hàng (CASA, NIM, tỷ lệ nợ xấu, số dư bình quân, tăng trưởng tín dụng) bằng cách chốt công thức một lần trong glossary rồi mã hoá vào lớp ngữ nghĩa; chú ý các "điểm dễ trôi dạt" (bình quân vs. cuối kỳ, gồm/không gồm...).
- Ba mức tăng tốc, tách bạch: query cache (BI tool), pre-aggregation (metrics layer), materialization (warehouse/dbt). Đẩy phần nặng và ổn định xuống warehouse.
- Quản trị định nghĩa như code: versioning (Git), review qua PR có nghiệp vụ duyệt, glossary có owner; tuyệt đối tránh copy-paste logic sang dashboard mới.
Bài viết liên quan
Phân biệt ước lượng điểm và ước lượng khoảng, cách xây khoảng tin cậy (CI) bằng margin of error z*·SE / t*·SE, đánh đổi mức 90/95/99%, và cách DIỄN GIẢI ĐÚNG khoảng tin cậy (95% CI không phải xác suất tham số nằm trong khoảng). Có CI cho trung bình và cho tỷ lệ, ảnh hưởng của cỡ mẫu, cùng ví dụ ngân hàng về tỷ lệ nợ xấu và số dư trung bình.
Kimball dimensional modeling: bảng fact/dimension, star vs snowflake, grain, và Slowly Changing Dimension.
Khối OLAP, các thao tác drill-down/roll-up/slice & dice/pivot, OLAP vs OLTP và ROLAP/MOLAP/HOLAP.
Từ nguồn dữ liệu qua ETL/ELT vào Data Warehouse, Data Mart đến dashboard; staging, ODS, batch vs streaming.