Data Modeling 1 — Nền tảng: OLTP vs OLAP, chuẩn hoá
Vì sao mô hình hoá dữ liệu quan trọng
Mô hình hoá dữ liệu (data modeling) là việc quyết định dữ liệu được tổ chức thành những bảng nào, mỗi bảng chứa gì, và các bảng nối với nhau ra sao. Nghe có vẻ là công việc kỹ thuật khô khan, nhưng đây là một trong những quyết định có sức ảnh hưởng dài hạn nhất trong bất kỳ hệ thống dữ liệu nào. Một mô hình tốt mang lại bốn thứ:
- Truy vấn dễ viết: người dùng nghiệp vụ và analyst hiểu ngay bảng nào để lấy con số nào, không phải đoán mò.
- Hiệu năng tốt: cấu trúc đúng cho phép engine tối ưu join, quét ít dữ liệu, dùng index/partition hiệu quả.
- Đúng đắn và nhất quán: cùng một chỉ tiêu (ví dụ "doanh thu tháng") cho cùng một kết quả ở mọi báo cáo, không có hai phiên bản sự thật.
- Khả năng mở rộng: thêm nguồn dữ liệu, thêm chiều phân tích mới mà không phải đập đi làm lại.
Ngược lại, một mô hình tồi thì mọi tầng phía trên đều gánh hậu quả: dashboard chậm, số liệu lệch nhau, pipeline dễ vỡ mỗi khi có thay đổi. Mô hình hoá chính là nền móng cho toàn bộ BI/analytics — nó là thứ mà dbt biến thành các bảng, mà BigQuery lưu và quét, và mà Looker hay Power BI trình bày cho người dùng cuối. Sai ở nền móng thì không có công cụ nào cứu được.
Bài này đặt nền tảng lý thuyết: phân biệt hai thế giới OLTP và OLAP, hiểu chuẩn hoá và phi chuẩn hoá, ba mức của một mô hình, và quy trình thiết kế cho kho dữ liệu. Các bài sau sẽ đào sâu vào mô hình chiều Kimball, bảng fact và dimension/SCD.
Toàn bộ SQL trong bài này là (minh hoạ) — mục đích để bạn thấy cấu trúc và ý tưởng thiết kế, không phải để chạy trong sandbox. Sandbox của loạt bài dùng PostgreSQL.
OLTP vs OLAP: hai thế giới khác nhau
Điểm khởi đầu quan trọng nhất là hiểu rằng có hai loại workload dữ liệu hoàn toàn khác nhau, và chúng đòi hỏi hai kiểu mô hình khác nhau.
OLTP (Online Transaction Processing) là hệ thống giao dịch — nơi ứng dụng chạy hằng ngày: hệ thống core banking ghi từng giao dịch, app thương mại điện tử tạo đơn hàng, CRM cập nhật thông tin khách hàng. Đặc trưng:
- Ghi nhiều (insert/update/delete liên tục), mỗi lần đụng tới rất ít dòng.
- Chuẩn hoá cao để mỗi sự thật chỉ nằm một chỗ, tránh cập nhật lệch nhau.
- Định hướng dòng (row-oriented): cần lấy trọn một bản ghi (một khách hàng, một đơn hàng).
- Ưu tiên tính nhất quán và toàn vẹn giao dịch (ACID), độ trễ thấp cho từng thao tác.
OLAP (Online Analytical Processing) là hệ thống phân tích — nơi analyst và báo cáo hỏi những câu tổng hợp: "doanh thu theo sản phẩm theo tháng trong 3 năm", "số khách hàng mới theo chi nhánh". Đặc trưng:
- Đọc nhiều, tổng hợp (aggregate) trên hàng triệu/tỷ dòng, ít ghi và thường ghi theo lô.
- Phi chuẩn hoá để giảm join, đọc nhanh.
- Định hướng cột (columnar): câu truy vấn thường chỉ đụng vài cột nhưng rất nhiều dòng — lưu theo cột giúp chỉ quét đúng cột cần và nén tốt. Đây chính là cách BigQuery và các kho dữ liệu hiện đại hoạt động.
- Ưu tiên throughput quét lớn hơn là độ trễ từng dòng.
| Tiêu chí | OLTP (giao dịch) | OLAP (phân tích) |
|---|---|---|
| Mục đích | Vận hành nghiệp vụ | Ra quyết định, báo cáo |
| Thao tác chính | Insert/Update nhiều dòng nhỏ | Select/aggregate nhiều dòng |
| Mô hình | Chuẩn hoá cao (3NF) | Phi chuẩn hoá (star schema) |
| Lưu trữ | Theo dòng (row) | Theo cột (columnar) |
| Câu truy vấn | Đơn giản, chạm ít dữ liệu | Phức tạp, quét khối lớn |
| Ví dụ | Postgres, MySQL, Oracle core | BigQuery, Snowflake, Redshift |
Vì sao không dùng schema OLTP để phân tích?
Một cám dỗ phổ biến là "cứ query thẳng vào database sản xuất cho nhanh". Đây là sai lầm tốn kém vì:
- Tải phân tích giết chết hệ thống giao dịch: một câu quét toàn bảng có thể làm chậm hoặc khoá tài nguyên mà app đang cần để phục vụ khách hàng.
- Schema OLTP chuẩn hoá cao đòi hỏi rất nhiều join: một báo cáo đơn giản có thể phải nối 10-15 bảng, vừa khó viết vừa chậm.
- Thiếu ngữ cảnh lịch sử: OLTP thường ghi đè giá trị mới nhất; để phân tích xu hướng ta cần lưu lịch sử thay đổi (chủ đề SCD).
- Lưu theo dòng không tối ưu cho aggregate: quét cột
so_tiencủa cả tỷ dòng trên storage theo dòng là rất lãng phí I/O.
Vì thế ta sao chép dữ liệu từ OLTP sang một hệ thống OLAP riêng (kho dữ liệu / lakehouse) và mô hình lại cho phù hợp phân tích. Đó là lý do tồn tại của cả ngành data engineering.
Chuẩn hoá (Normalization)
Chuẩn hoá là quy trình cấu trúc lại các bảng để giảm dư thừa dữ liệu và tránh các bất thường (anomalies). Ba loại bất thường điển hình khi dữ liệu bị lặp:
- Insert anomaly: không thêm được thông tin sản phẩm nếu chưa có đơn hàng nào chứa nó.
- Update anomaly: đổi tên khách hàng phải sửa ở hàng trăm dòng đơn hàng; sót một dòng là dữ liệu mâu thuẫn.
- Delete anomaly: xoá đơn hàng cuối cùng của một khách hàng vô tình mất luôn thông tin khách hàng đó.
Chuẩn hoá diễn ra theo các dạng chuẩn (normal forms) tăng dần độ nghiêm ngặt.
1NF — First Normal Form
Mỗi ô chứa một giá trị nguyên tử (atomic), không có nhóm lặp hay danh sách nhồi trong một cột, và mỗi dòng nhận diện được duy nhất.
-- VI PHẠM 1NF: cột dien_thoai chứa nhiều số
-- khach_hang(id, ho_ten, dien_thoai)
-- (1, 'An', '0901111111, 0902222222')
-- ĐẠT 1NF: tách thành các dòng/ bảng con nguyên tử
CREATE TABLE khach_hang (id INT PRIMARY KEY, ho_ten TEXT);
CREATE TABLE dien_thoai (
khach_hang_id INT REFERENCES khach_hang(id),
so_dt TEXT,
PRIMARY KEY (khach_hang_id, so_dt)
);
2NF — Second Normal Form
Đạt 1NF, và không có phụ thuộc bộ phận (partial dependency) — nghĩa là khi khoá chính là khoá tổ hợp, mọi thuộc tính không-khoá phải phụ thuộc vào toàn bộ khoá, chứ không chỉ một phần.
-- VI PHẠM 2NF: khoá là (don_hang_id, san_pham_id)
-- nhưng ten_san_pham chỉ phụ thuộc san_pham_id (một phần khoá)
-- chi_tiet_don(don_hang_id, san_pham_id, so_luong, ten_san_pham)
-- ĐẠT 2NF: tách thuộc tính của sản phẩm ra bảng riêng
CREATE TABLE san_pham (
san_pham_id INT PRIMARY KEY,
ten_san_pham TEXT
);
CREATE TABLE chi_tiet_don (
don_hang_id INT,
san_pham_id INT REFERENCES san_pham(san_pham_id),
so_luong INT,
PRIMARY KEY (don_hang_id, san_pham_id)
);
3NF — Third Normal Form
Đạt 2NF, và không có phụ thuộc bắc cầu (transitive dependency) — thuộc tính không-khoá không được phụ thuộc vào một thuộc tính không-khoá khác.
-- VI PHẠM 3NF: don_hang -> chi_nhanh_id -> ten_chi_nhanh
-- ten_chi_nhanh phụ thuộc bắc cầu qua chi_nhanh_id
-- don_hang(id, ngay, chi_nhanh_id, ten_chi_nhanh, dia_chi_chi_nhanh)
-- ĐẠT 3NF: đưa thuộc tính chi nhánh về đúng thực thể chi nhánh
CREATE TABLE chi_nhanh (
chi_nhanh_id INT PRIMARY KEY,
ten_chi_nhanh TEXT,
dia_chi TEXT
);
CREATE TABLE don_hang (
id INT PRIMARY KEY,
ngay DATE,
chi_nhanh_id INT REFERENCES chi_nhanh(chi_nhanh_id)
);
BCNF (Boyce-Codd Normal Form) là biến thể chặt hơn 3NF: với mọi phụ thuộc hàm X → Y, X phải là siêu khoá (super key). BCNF xử lý các ca hiếm mà 3NF còn để lọt, thường liên quan đến bảng có nhiều khoá ứng viên chồng lấn. Trong thực tế thiết kế OLTP thông thường, đạt tới 3NF là đủ cho phần lớn trường hợp; BCNF/4NF/5NF chủ yếu là chủ đề học thuật hoặc cho các mô hình đặc biệt.
Khi nào dừng? Nguyên tắc kinh điển: "normalize until it hurts, denormalize until it works". Với hệ thống giao dịch, dừng ở 3NF thường là điểm cân bằng tốt giữa toàn vẹn dữ liệu và độ phức tạp truy vấn.
Phi chuẩn hoá (Denormalization)
Phi chuẩn hoá là cố ý đưa dữ liệu trở lại trạng thái trùng lặp có kiểm soát để đọc nhanh hơn. Đây không phải là "làm ẩu" — nó là một đánh đổi có chủ đích, phù hợp cho kho dữ liệu và analytics:
- Trong OLAP, ta đọc nhiều hơn ghi rất nhiều lần, nên chi phí duy trì bản sao đồng nhất thấp hơn lợi ích tránh join.
- Engine columnar nén tốt các cột lặp lại, nên trùng lặp không tốn nhiều dung lượng như trực giác.
- Ít join hơn nghĩa là câu truy vấn đơn giản hơn cho analyst và nhanh hơn trên khối dữ liệu lớn.
-- CHUẨN HOÁ (OLTP): 3 bảng, muốn báo cáo phải join
-- don_hang(id, khach_hang_id, chi_nhanh_id, so_tien)
-- khach_hang(id, ho_ten, phan_khuc)
-- chi_nhanh(id, ten_chi_nhanh, vung)
-- PHI CHUẨN HOÁ (OLAP): một bảng phẳng, đọc thẳng
CREATE TABLE fact_don_hang_flat (
don_hang_id BIGINT,
ngay DATE,
khach_hang TEXT, -- lặp lại tên khách theo mỗi đơn
phan_khuc TEXT, -- lặp lại
chi_nhanh TEXT, -- lặp lại
vung TEXT, -- lặp lại
so_tien NUMERIC
);
-- SELECT vung, SUM(so_tien) FROM fact_don_hang_flat GROUP BY vung;
Cái giá phải trả là trách nhiệm giữ đồng nhất chuyển sang pipeline ETL/ELT: khi khách đổi tên, ta phải cập nhật ở nơi tạo bản sao. Trong kho dữ liệu, thay vì làm một bảng phẳng khổng lồ, ta thường dùng star schema — vừa giảm join, vừa quản lý được trùng lặp.
Ba mức mô hình
Một mô hình dữ liệu được thể hiện ở ba mức trừu tượng, đi từ nghiệp vụ tới vật lý:
- Conceptual (khái niệm): mô tả thực thể và quan hệ ở góc nhìn nghiệp vụ, không quan tâm chi tiết kỹ thuật. Ví dụ: "Khách hàng đặt Đơn hàng; Đơn hàng chứa Sản phẩm". Công cụ điển hình là ERD (Entity-Relationship Diagram). Ai cũng đọc hiểu, kể cả người không kỹ thuật.
- Logical (logic): bổ sung thuộc tính, khoá chính (PK), khoá ngoại (FK), kiểu quan hệ — nhưng độc lập với DBMS cụ thể. Đây là bản thiết kế chi tiết chưa gắn với Postgres hay BigQuery.
- Physical (vật lý): hiện thực hoá cho một DBMS cụ thể — chọn kiểu dữ liệu (
NUMERIC(18,2)vsDECIMAL), đặt index, chọn chiến lược partition/clustering, cân nhắc hiệu năng lưu trữ. Ở BigQuery đây là nơi ta quyết định partition theo ngày, cluster theo cột lọc.
Tách ba mức giúp trao đổi với đúng đối tượng: bàn nghiệp vụ ở mức conceptual, thiết kế cùng team dữ liệu ở mức logical, tối ưu cùng DBA/engineer ở mức physical.
ERD và quan hệ
Ở mức conceptual/logical, các khái niệm cốt lõi:
- Entity (thực thể): đối tượng nghiệp vụ — Khách hàng, Tài khoản, Giao dịch. Thành bảng ở mức physical.
- Attribute (thuộc tính): đặc điểm của thực thể — họ tên, số dư, ngày mở.
- PK (Primary Key): định danh duy nhất một dòng.
- FK (Foreign Key): tham chiếu tới PK của thực thể khác, tạo nên quan hệ.
- Cardinality (bản số): mức độ của quan hệ:
- 1-1: một khách hàng có một hồ sơ định danh.
- 1-n: một khách hàng có nhiều tài khoản.
- n-n: một sản phẩm thuộc nhiều đơn, một đơn có nhiều sản phẩm — thường tách bằng một bảng nối (junction table).
Đây là một mô hình chuẩn hoá kiểu OLTP: khách hàng — tài khoản — giao dịch nối nhau bằng FK, mỗi sự thật một chỗ.
Star schema vs 3NF cho kho dữ liệu
Khi chuyển sang kho dữ liệu, ta thường không giữ mô hình 3NF của OLTP. Thay vào đó dùng dimensional modeling / star schema: một bảng fact ở giữa (các số đo lường được: số tiền, số lượng) nối với các bảng dimension xung quanh (ngữ cảnh mô tả: ai, cái gì, khi nào, ở đâu). Ít join, dễ hiểu, tối ưu cho engine cột. Chi tiết đầy đủ nằm ở bài Dimensional/Kimball và BI mô hình chiều.
Có hai trường phái lớn về kiến trúc kho:
- Inmon (top-down): xây một Enterprise Data Warehouse chuẩn hoá 3NF làm "một nguồn sự thật" tích hợp toàn doanh nghiệp, rồi từ đó tạo các data mart chiều cho từng nhóm nghiệp vụ. Nghiêm ngặt, tích hợp cao, nhưng xây lâu và tốn kém.
- Kimball (bottom-up): xây trực tiếp các star schema hướng câu hỏi nghiệp vụ, các mart chia sẻ dimension nhất quán (conformed dimensions). Ra giá trị nhanh, thân thiện với analyst — và là cách tiếp cận phổ biến nhất trong analytics hiện đại.
Trong Inmon, 3NF EDW không phải là nơi analyst query trực tiếp — nó là kho sạch, tích hợp, từ đó dẫn xuất ra các mart chiều. So sánh sâu ba trường phái (kể cả Data Vault) nằm ở bài Mô hình hoá kho dữ liệu nâng cao.
Quy trình mô hình hoá cho kho dữ liệu
Một quy trình thực tế theo tinh thần Kimball, gồm năm bước:
- Hiểu nghiệp vụ và câu hỏi cần trả lời: bắt đầu từ quy trình nghiệp vụ (business process) — ví dụ "giao dịch tài khoản" — và những câu hỏi phân tích quanh nó. Đừng mô hình hoá thứ chưa ai hỏi.
- Chọn grain (độ hạt): xác định một dòng fact biểu diễn cái gì. Ví dụ "mỗi dòng là một giao dịch". Grain là quyết định quan trọng nhất; chọn sai grain thì mọi thứ phía sau đều sai. (Chi tiết ở bài Fact tables.)
- Xác định dimension (thực thể ngữ cảnh): theo grain đã chọn, dữ liệu mô tả là gì — khách hàng, tài khoản, ngày, kênh, sản phẩm.
- Xác định fact (số đo): những giá trị đo lường được ở đúng grain — số tiền, phí, số lượng.
- Vật lý hoá: chuyển sang mức physical — kiểu dữ liệu, khoá thay thế cho dimension, partition bảng fact theo ngày, cluster theo cột lọc thường dùng.
Khoá tự nhiên vs khoá thay thế
Một quyết định thiết kế cốt lõi khi vật lý hoá kho:
- Khoá tự nhiên (natural key): định danh có sẵn từ nghiệp vụ — số CIF, số tài khoản, mã sản phẩm. Có ý nghĩa nhưng có thể đổi, có thể dài, và đôi khi không thực sự duy nhất theo thời gian.
- Khoá thay thế (surrogate key): một số nguyên vô nghĩa do kho tự sinh (thường tăng dần), làm PK cho dimension. Ổn định, gọn, không phụ thuộc thay đổi nghiệp vụ, và — quan trọng — cho phép lưu nhiều phiên bản lịch sử của cùng một thực thể (nền tảng của SCD).
Trong kho dữ liệu, thực hành chuẩn là dùng surrogate key làm PK của dimension và làm FK trong fact, đồng thời vẫn giữ natural key như một thuộc tính để đối chiếu về nguồn.
Use case thực tế: từ bảng OLTP giao dịch sang mô hình phân tích
Giả sử core banking có bảng giao dịch OLTP chuẩn hoá như ERD ở trên: giao_dich nối tai_khoan nối khach_hang. Phòng phân tích cần báo cáo: tổng số tiền và số lượng giao dịch theo phân khúc khách hàng, theo kênh, theo tháng.
Nếu query thẳng OLTP, ta phải join ba bảng, chạy aggregate nặng trên hệ thống đang phục vụ khách — không nên. Thay vào đó, ta mô hình lại thành star schema:
- Grain: mỗi dòng fact là một giao dịch.
- Dimension:
dim_khach_hang(có phân khúc),dim_ngay(có tháng/quý/năm),dim_kenh. - Fact:
fact_giao_dichgiữso_tien,phi, và các surrogate FK tới dimension.
CREATE TABLE dim_khach_hang (
khach_hang_sk BIGINT PRIMARY KEY, -- surrogate key
cif TEXT, -- natural key giữ để đối chiếu
ho_ten TEXT,
phan_khuc TEXT
);
CREATE TABLE fact_giao_dich (
giao_dich_sk BIGINT PRIMARY KEY,
khach_hang_sk BIGINT REFERENCES dim_khach_hang(khach_hang_sk),
ngay_sk INT, -- FK tới dim_ngay
kenh_sk BIGINT, -- FK tới dim_kenh
so_tien NUMERIC(18,2),
phi NUMERIC(18,2)
);
-- Báo cáo trở nên đơn giản và nhanh:
-- SELECT k.phan_khuc, d.thang, SUM(f.so_tien), COUNT(*)
-- FROM fact_giao_dich f
-- JOIN dim_khach_hang k ON f.khach_hang_sk = k.khach_hang_sk
-- JOIN dim_ngay d ON f.ngay_sk = d.ngay_sk
-- GROUP BY k.phan_khuc, d.thang;
Pipeline dbt sẽ chịu trách nhiệm sao chép dữ liệu từ OLTP, sinh surrogate key, và nạp các bảng này theo lô. Kết quả: báo cáo nhanh, không đụng hệ thống sản xuất, và có thể mở rộng thêm chiều (sản phẩm, chi nhánh) mà không phá cấu trúc.
Ghi nhớ
- OLTP và OLAP là hai thế giới khác nhau: giao dịch cần chuẩn hoá cao, lưu theo dòng, ghi nhiều; phân tích cần phi chuẩn hoá, lưu theo cột, đọc/aggregate nhiều. Đừng phân tích trên schema OLTP.
- Chuẩn hoá (1NF → 3NF) giảm dư thừa và tránh anomaly; với OLTP thường dừng ở 3NF. BCNF là biến thể chặt hơn cho ca hiếm.
- Phi chuẩn hoá là đánh đổi có chủ đích cho analytics: trùng lặp có kiểm soát đổi lấy đọc nhanh; columnar nén tốt nên chi phí dung lượng thấp.
- Ba mức mô hình: conceptual (thực thể/quan hệ), logical (thuộc tính/khoá, độc lập DBMS), physical (kiểu dữ liệu, index, partition theo DBMS).
- Star schema cho kho: fact ở giữa, dimension xung quanh. Kimball (dimensional, bottom-up) phổ biến hơn Inmon (3NF EDW, top-down) trong analytics hiện đại.
- Quy trình: nghiệp vụ/câu hỏi → grain → dimension → fact → vật lý hoá.
- Trong kho, dùng surrogate key làm PK dimension và FK trong fact; giữ natural key để đối chiếu nguồn và làm nền cho lịch sử (SCD).
Bài tiếp theo: Dimensional Modeling & Kimball đi sâu vào star schema, conformed dimensions và bus matrix.
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.