Modeling nâng cao 4 — Data Vault 2.0 chuyên sâu

22 thg 7, 2026 3 lượt xem
#data-engineering
#data-vault
#data-modeling
#hash-key
#pit-bridge

Bài này giả định bạn đã đọc Data Vault 2.0 cơ bản — biết Hub là danh sách business key, Link là quan hệ n–n, Satellite giữ thuộc tính + lịch sử append-only. Ở đây ta không nhắc lại những điều đó mà đi vào phần khó: hash key/hash diff làm đúng, các biến thể satellite mà thực tế bắt buộc phải có, cấu trúc tăng tốc truy vấn (PIT/Bridge), và cơ chế nạp song song insert-only khiến Data Vault khác biệt. Bài nằm trong series Data Modeling nâng cao, kế thừa kỹ thuật lịch sử ở SCD nâng cao và dùng lại ở mô hình hoá ngân hàng.

Mọi DDL/SQL trong bài là (minh hoạ) cho sandbox PostgreSQL — để thấy cấu trúc, không nhằm chạy end-to-end.

Mô hình tinh thần: ba tốc độ thay đổi + hai lớp

Data Vault chuẩn (DV2.0) đặt mọi thứ lên hai trục. Trục thứ nhất là tốc độ thay đổi: khoá nghiệp vụ (bất biến) → Hub; quan hệ (thay đổi vừa) → Link; thuộc tính (đổi liên tục) → Satellite. Trục thứ hai là mức độ diễn giải: dữ liệu thô trung thực → Raw Vault; dữ liệu đã áp business rule + cấu trúc tăng tốc → Business Vault. Toàn bộ kỹ thuật nâng cao dưới đây đều là hệ quả của việc tôn trọng chặt chẽ hai trục này — đặc biệt là ràng buộc insert-only (không UPDATE, không DELETE) khiến ta phải phát minh ra effectivity satellite, PIT, bridge thay vì đơn giản sửa bản ghi tại chỗ.

Hash key làm đúng: chuẩn hoá, va chạm, ghost record

Ở bài cơ bản ta đã biết DV2.0 dùng hash của business key thay surrogate tuần tự để nạp song song. Phần nâng cao là làm sao cho hash tất định và an toàn:

  • Chuẩn hoá trước khi hash (canonicalization) là bắt buộc, nếu không cùng một khoá cho hai hash khác nhau ở hai hệ thống. Quy ước: trim, UPPER, chuyển NULL về chuỗi rỗng nhất quán, và với khoá phức hợp phải nối các cột theo thứ tự cố định với một dấu phân tách hiếm gặp (thường ký tự điều khiển hoặc ||, ví dụ ^^) để tránh nhập nhằng biên ('AB'||'C' vs 'A'||'BC').
  • Va chạm hash (collision): MD5 (128-bit) là lựa chọn kinh điển của Linstedt vì nhanh và đủ rộng; xác suất va chạm ngẫu nhiên là cực nhỏ ở quy mô kho. Ai lo về bảo mật/tương lai thì dùng SHA-256, đổi lại tốn lưu trữ (64 hex) và CPU. Điểm cần nhớ: hash không phải để bảo mật mà để phân phối và song song hoá.
  • Ghost record / zero key: mỗi satellite nên có sẵn một bản ghi "ma" với hash key toàn số 0 ('000...0') và các thuộc tính NULL/mặc định. Lý do sẽ rõ ở phần PIT: nó cho phép equi-join (thậm chí INNER JOIN) luôn tìm được một hàng, kể cả khi một satellite chưa có dữ liệu cho khoá đó tại thời điểm snapshot — tránh việc LEFT JOIN làm hỏng tốc độ.

Hash diff: bắt thay đổi trong một phép so

Hash diff (hashdiff) là hash của toàn bộ cột mô tả trong một satellite, dùng để phát hiện thay đổi mà không phải so từng cột. Các lỗi kinh điển và cách làm đúng:

  • Thứ tự cột và delimiter phải cố định — nếu không, cùng dữ liệu sinh hash diff khác nhau giữa các lần chạy hoặc giữa các môi trường.
  • Xử lý NULL nhất quán: dùng COALESCE(col,'') để NULL và chuỗi rỗng không bị nhầm là "thay đổi" qua lại.
  • Delimiter chống nhập nhằng biên: nối col_a || '^^' || col_b để tránh ('a','bc')('ab','c') cho cùng hash.
  • Hash diff chỉ tính trên cột mô tả của chính satellite đó, không gồm load date/record source (metadata nạp không phải "thay đổi nghiệp vụ").

Các loại Satellite nâng cao

Satellite "chuẩn" (một hàng hiện hành cho mỗi khoá, append theo hashdiff) không đủ cho mọi tình huống. DV2.0 định nghĩa vài biến thể quan trọng:

Multi-active satellite (MAS)

Khi một khoá cha có nhiều giá trị đồng thời hợp lệ tại cùng một thời điểm — ví dụ một khách hàng có nhiều số điện thoại, nhiều địa chỉ, nhiều email — satellite chuẩn (PK = hash key + load date) sẽ ép chỉ một hàng/khoá/thời điểm và làm mất dữ liệu. Multi-active satellite thêm một multi-active key (subsequence) vào khoá chính: PK = (hash key, multi_active_key, load_date). Subsequence có thể là số thứ tự nội bộ hoặc chính một thuộc tính phân biệt (loại liên hệ). Lưu ý nạp: hash diff của MAS thường tính trên toàn bộ tập con đang hoạt động của khoá đó (hoặc từng dòng, tuỳ chuẩn nhóm) — điểm dễ sai nhất là để hai lần nạp giống hệt lại sinh bản ghi mới vì subsequence bị đánh lại số khác nhau.

Effectivity satellite

Đây là mảnh ghép quan trọng nhất mà người mới thường bỏ sót. Vì Link là insert-only (không UPDATE/DELETE), khi một quan hệ chấm dứt — khách hàng đóng tài khoản, đổi người quản lý quan hệ (RM) — ta không thể xoá hàng trong link. Thay vào đó, effectivity satellite gắn với link ghi nhận hiệu lực của quan hệ theo thời gian: cột hieu_luc_tu/hieu_luc_den (hoặc cờ dang_hoat_dong).

Khái niệm cốt lõi là driving key: tập hub-key quyết định "chỉ một quan hệ được hoạt động tại một thời điểm". Ví dụ quan hệ "tài khoản có RM phụ trách" với driving key = tài khoản: khi tài khoản chuyển sang RM mới, quá trình nạp effectivity satellite đóng (end-date) hàng cũ và mở hàng mới — tất cả bằng INSERT append-only, không sửa bản ghi. Nhờ vậy link vẫn giữ mọi quan hệ từng tồn tại, còn effectivity satellite cho biết cái nào đang đúng.

Với các sự kiện bất biến — một giao dịch chuyển khoản, một lần quẹt thẻ, một log — quan hệ không bao giờ thay đổi sau khi phát sinh. DV2.0 dùng non-historized link (còn gọi transactional link): link nối các hub liên quan kèm luôn các thuộc tính đo lường bất biến của sự kiện (số tiền, thời điểm) hoặc gắn một non-historized satellite, và không cần effectivity/hashdiff vì sự kiện không có "phiên bản mới". Nạp thuần insert, không so hashdiff. Đây là điểm khác biệt với quan hệ "trạng thái" (như sở hữu tài khoản) vốn cần effectivity satellite.

Ngoài ra còn các satellite chuyên dụng: status tracking satellite (theo dõi cờ CDC I/U/D từ nguồn), record tracking satellite (ghi nhận mỗi lần một khoá xuất hiện trong một lần nạp, kể cả khi không đổi) — dùng cho audit "nguồn có còn gửi khoá này không".

-- Multi-active satellite: nhiều liên hệ đồng thời cho 1 khách hàng
CREATE TABLE msat_kh_lienhe (
    hk_kh        CHAR(32)    NOT NULL,
    multi_key    VARCHAR(30) NOT NULL,     -- subsequence: loại liên hệ
    ldts         TIMESTAMP   NOT NULL,
    hashdiff     CHAR(32)    NOT NULL,
    rsrc         VARCHAR(50) NOT NULL,
    loai_lienhe  VARCHAR(20),              -- MOBILE / EMAIL / ...
    gia_tri      VARCHAR(200),
    PRIMARY KEY (hk_kh, multi_key, ldts),  -- KHOÁ 3 phần: cho phép n giá trị/thời điểm
    FOREIGN KEY (hk_kh) REFERENCES hub_kh(hk_kh)
);

-- Effectivity satellite cho link "tài khoản có RM": insert-only, dùng driving key
CREATE TABLE esat_tk_rm (
    hk_link      CHAR(32)    NOT NULL,      -- hash(so_tk ^^ ma_rm)
    ldts         TIMESTAMP   NOT NULL,
    rsrc         VARCHAR(50) NOT NULL,
    hieu_luc_tu  TIMESTAMP   NOT NULL,
    hieu_luc_den TIMESTAMP,                 -- NULL = đang hoạt động
    dang_hoat_dong BOOLEAN   NOT NULL,
    PRIMARY KEY (hk_link, ldts),
    FOREIGN KEY (hk_link) REFERENCES link_tk_rm(hk_link)
);

-- Non-historized link: giao dịch chuyển khoản (bất biến), thuộc tính đo ngay trên link
CREATE TABLE link_giao_dich (
    hk_gd        CHAR(32)    NOT NULL,      -- hash(ma_giao_dich)
    hk_tk_nguon  CHAR(32)    NOT NULL,
    hk_tk_dich   CHAR(32)    NOT NULL,
    ldts         TIMESTAMP   NOT NULL,
    rsrc         VARCHAR(50) NOT NULL,
    thoi_diem_gd TIMESTAMP   NOT NULL,      -- business time
    so_tien      NUMERIC(18,2) NOT NULL,
    PRIMARY KEY (hk_gd),                    -- 1 giao dịch = 1 hàng, không phiên bản
    FOREIGN KEY (hk_tk_nguon) REFERENCES hub_tk(hk_tk),
    FOREIGN KEY (hk_tk_dich)  REFERENCES hub_tk(hk_tk)
);

Nạp song song & idempotent: vì sao insert-only là chìa khoá

Data Vault nạp theo nguyên tắc insert-onlyidempotent — chạy lại cùng một batch không tạo dữ liệu sai. Ba mẫu nạp:

  • Hub: chèn khoá chưa tồn tại (WHERE NOT EXISTS) — chạy lại không nhân bản.
  • Satellite chuẩn: chèn khi hash diff khác bản ghi hiện hành — không đổi thì không sinh hàng thừa.
  • Effectivity satellite: so trạng thái hiệu lực theo driving key; chỉ chèn khi quan hệ đang-hoạt-động đổi.

Vì hash key tính trực tiếp từ business key, hub, link, satellite nạp được đồng thời — không có phụ thuộc lookup tuần tự. Đây là điều khiến Data Vault mở rộng tốt trên Spark/warehouse cột: mọi node tính hash cục bộ, không cần điều phối bộ sinh số tập trung.

-- Effectivity: đóng hàng cũ + mở hàng mới đều bằng INSERT (không UPDATE)
INSERT INTO esat_tk_rm (hk_link, ldts, rsrc, hieu_luc_tu, hieu_luc_den, dang_hoat_dong)
SELECT md5(s.so_tk||'^^'||s.ma_rm), now(), 'CORE', now(), NULL, true
FROM stg_tk_rm s
LEFT JOIN LATERAL (          -- trạng thái hiện hành theo DRIVING KEY = tài khoản
    SELECT e.hk_link, e.dang_hoat_dong
    FROM esat_tk_rm e
    JOIN link_tk_rm l ON l.hk_link = e.hk_link
    WHERE l.hk_tk = md5(s.so_tk)          -- cùng tài khoản
    ORDER BY e.ldts DESC LIMIT 1
) cur ON true
WHERE cur.hk_link IS DISTINCT FROM md5(s.so_tk||'^^'||s.ma_rm)  -- RM thực sự đổi
   OR cur.dang_hoat_dong IS DISTINCT FROM true;

PIT và Bridge: tăng tốc truy vấn

Cái giá của Data Vault là truy vấn phức tạp: một hub nhiều satellite với load date lệch nhau, muốn lấy "trạng thái tại thời điểm T" phải join nhiều satellite rồi lọc MAX(ldts) <= T cho từng cái. Business Vault giải bằng hai cấu trúc query-assistance:

PIT (Point-In-Time) table

PIT là bảng snapshot dựng sẵn (thường theo lịch đêm). Với mỗi khoá hub và mỗi snapshot date, PIT lưu load date của bản ghi active trong từng satellite tại thời điểm đó. Nhờ vậy truy vấn "trạng thái tại T" biến từ nhiều bất-đẳng-thức thành equi-join trên (hash key, ldts) — cực nhanh. Đây là lúc ghost record phát huy: nếu một satellite chưa có dữ liệu tại T, PIT trỏ về ghost key thay vì NULL, cho phép INNER JOIN đồng đều không rơi hàng.

Bridge table

Nếu PIT giải quyết nhiều satellite trên một hub, thì Bridge giải quyết đi qua nhiều link/hub: khách hàng → tài khoản → giao dịch → sản phẩm. Bridge dựng sẵn đường đi (pre-join qua chuỗi link) và cũng gắn snapshot date, giảm số join khi phục vụ. Bridge thường là bàn đạp để vật chất hoá fact trong information mart Kimball.

-- Truy vấn nhờ PIT: chỉ equi-join, không bất đẳng thức
SELECT p.snapshot_date, c.ho_ten, r.phan_khuc
FROM pit_kh p
JOIN sat_kh_core c ON c.hk_kh = p.hk_kh AND c.ldts = p.ldts_core   -- equi-join
JOIN sat_kh_crm  r ON r.hk_kh = p.hk_kh AND r.ldts = p.ldts_crm    -- equi-join
WHERE p.snapshot_date = DATE '2026-06-30';

Raw Vault vs Business Vault: ranh giới trách nhiệm

Ranh giới này quyết định khả năng audit của cả kho, nên phải nghiêm ngặt:

Khía cạnhRaw VaultBusiness Vault
Nội dungBản sao trung thực của nguồn, kể cả dữ liệu "xấu"Dữ liệu đã áp business rule
Business ruleKhông áp (chỉ tách + hash)Có: chuẩn hoá, hợp nhất, tính toán
Tính bất biếnBất biến, insert-onlyCó thể tái tạo từ raw vault
Vai trò auditNền tảng "nguồn thực sự nói gì, khi nào"Không phải nguồn sự thật gốc
Cấu trúc đặc thùHub/Link/Satellite thôComputed sat, derived link, PIT/Bridge
Khi rule saiGiữ nguyênSửa rule + nạp lại từ raw

Nguyên tắc vàng: PIT, Bridge, computed satellite luôn thuộc Business Vault — chúng là dẫn xuất, tái tạo được, không được lẫn vào raw vault. Còn raw vault phải trung thực tuyệt đối: nếu sau này phát hiện rule tính sai, ta sửa rule ở business vault và nạp lại từ raw, không mất dữ liệu gốc. Đó chính là lý do Data Vault mạnh về audit và reprocessing.

Khi nào dùng Data Vault (và khi nào không)

Data Vault nâng cao xứng đáng khi hội đủ: nhiều nguồn định danh khác nhau cần tích hợp; schema đổi liên tục (thêm nguồn không được đập vỡ mô hình); yêu cầu audit/truy vết nghiêm ngặt (ngân hàng, bảo hiểm, y tế); và cần nạp song song quy mô lớn. Effectivity satellite, MAS, PIT/Bridge chỉ trả về giá trị khi độ phức tạp thực sự cao.

Ngược lại, kho ít nguồn, schema ổn định, audit vừa phải thì cái giá "rất nhiều bảng + luôn cần mart + phải học phương pháp luận" là không tương xứng — hãy dựng thẳng Kimball với SCD cho lịch sử. Và dù chọn Data Vault, người dùng cuối vẫn query trên mart Kimball phía trên, không bao giờ query trực tiếp vault.

Use case thực tế

Bối cảnh (minh hoạ, NCB). Kho tích hợp khách hàng từ core banking (CIF), CRM (email/CMND) và hệ thống thẻ (mã nội bộ); pháp chế yêu cầu truy vết mọi thay đổi thông tin liên hệ và lịch sử người quản lý quan hệ (RM).

  • Multi-active satellite msat_kh_lienhe: một khách hàng có 3 số điện thoại + 2 email cùng lúc — MAS giữ đủ, không ép về một hàng. Giả sử ~2 triệu khách, trung bình 2,5 liên hệ/khách → ~5 triệu hàng đang hoạt động, vẫn nạp song song trong cửa sổ EOD.
  • Effectivity satellite esat_tk_rm: khi tài khoản doanh nghiệp đổi RM, hàng cũ được end-date và hàng mới mở — tất cả bằng INSERT. Audit truy được "tài khoản X do RM nào phụ trách vào 30/06".
  • Non-historized link link_giao_dich: ~40 triệu giao dịch/ngày nạp thuần insert, không hashdiff — nhanh và bất biến.
  • PIT pit_kh snapshot cuối tháng: báo cáo phân khúc khách hàng tại 30/06 chạy bằng equi-join qua PIT thay vì quét nhiều satellite — thời gian truy vấn giảm rõ rệt (minh hoạ).
  • Mart Kimball: business vault hợp nhất 3 nguồn (ưu tiên core khi mâu thuẫn) → dim_khach_hang để BI dùng, không thấy độ phức tạp bên dưới. Xem thêm mô hình hoá ngân hàng.

Ghi nhớ

  • Hash key cần canonicalization (trim/upper/null nhất quán, delimiter cố định); MD5 đủ dùng, SHA-256 khi cần; hash để song song hoá, không phải bảo mật.
  • Ghost record (zero key) trong mỗi satellite cho phép equi/INNER-join với PIT không rơi hàng.
  • Multi-active satellite: thêm subsequence key vào PK để giữ nhiều giá trị đồng thời (nhiều số điện thoại/địa chỉ).
  • Effectivity satellite + driving key: theo dõi quan hệ đang hiệu lực vì Link insert-only (không UPDATE/DELETE).
  • Non-historized (transactional) link cho sự kiện bất biến — nạp thuần insert, không hashdiff/effectivity.
  • PIT biến "trạng thái tại T" thành equi-join; Bridge pre-join qua nhiều link/hub. Cả hai thuộc Business Vault.
  • Raw Vault = trung thực, bất biến, audit; Business Vault = áp rule + PIT/Bridge, tái tạo được, nạp lại khi rule sai.
  • Nạp insert-only + idempotentsong song 100% hub/link/satellite; chỉ dùng Data Vault khi nhiều nguồn + đổi liên tục + audit nghiêm.

Nguồn tham khảo

  • Daniel Linstedt & Michael Olschimke — Building a Scalable Data Warehouse with Data Vault 2.0 (Morgan Kaufmann, 2015): định nghĩa gốc Hub/Link/Satellite, hash key, các loại satellite, PIT/Bridge và phương pháp luận nạp.
  • Data Vault Alliance — cộng đồng và chuẩn phương pháp luận do Dan Linstedt sáng lập (effectivity, driving key, non-historized link).
  • AutomateDV Documentation — package dbt generate Hub/Link/Sat, multi-active satellite, effectivity satellite, PIT và Bridge từ metadata.
  • dbt Documentation — nền tảng transform thường dùng để tự động hoá sinh code Data Vault theo mẫu.
  • Ralph Kimball & Margy Ross — The Data Warehouse Toolkit (Wiley): tham chiếu cho tầng information mart star schema đặt phía trên Data Vault.
  • Joe Reis & Matt Housley — Fundamentals of Data Engineering (O'Reilly, 2022): bối cảnh kiến trúc kho hiện đại và vị trí của các phương pháp mô hình hoá.

Bài viết liên quan

So sánh các định dạng dữ liệu (CSV, JSON, XML, Avro, Parquet, ORC) và lý do lưu theo cột nhanh hơn cho phân tích. Bài đi sâu vào row vs columnar storage, nén (Snappy/gzip/zstd), schema evolution, OLTP vs OLAP, object storage và partitioning để tối ưu chi phí lẫn tốc độ truy vấn.

13 thg 7, 2026 10

Data Engineering là ngành xây dựng và vận hành hệ thống biến dữ liệu thô thành dữ liệu sạch, tin cậy, sẵn sàng cho phân tích và AI. Bài giới thiệu vai trò Data Engineer trong vòng đời dữ liệu (nguồn → ingestion → storage → transformation → serving), phân biệt với Analyst/Scientist/ML Engineer, bức tranh hệ sinh thái công cụ và bài toán đưa dữ liệu core banking sang kho phân tích.

13 thg 7, 2026 9

Stream processing là gì, khác biệt batch vs stream (bounded/unbounded), micro-batch (Spark) vs true streaming, Apache Flink là gì và định vị so với Spark Structured Streaming và Kafka Streams. Kiến trúc runtime JobManager/TaskManager, các tầng API, triết lý streaming-first và bối cảnh phát hiện gian lận ngân hàng.

13 thg 7, 2026 8

Vì sao một máy không đủ và cần xử lý phân tán: từ MapReduce, Hadoop/HDFS đến Apache Spark in-memory. Kiến trúc driver–executor–cluster manager, các mức trừu tượng RDD/DataFrame/Dataset, cơ chế lazy evaluation với DAG, và vì sao shuffle (wide dependency) là phần tốn kém nhất. Kèm PySpark, Spark SQL và các kỹ thuật tối ưu (partition, broadcast join, cache, chống skew) cùng khi nào KHÔNG nên dùng Spark.

13 thg 7, 2026 7

Cảm nhận của bạn

Bình luận

Bạn cần để viết bình luận.

Chưa có bình luận. Hãy là người đầu tiên chia sẻ!