Dimensional modeling — star & snowflake schema
Vì sao warehouse cố ý KHÔNG chuẩn hoá như database giao dịch? Fact table, dimension table, star vs snowflake schema và đánh đổi denormalize — agnostic.
TL;DR: Warehouse OLAP không chuẩn hoá như database giao dịch mà tổ chức dữ liệu quanh sự kiện cần đo. Fact table lưu các sự kiện đo lường được — mỗi dòng là một lần xảy ra, gồm các số đo (numeric measures) và foreign key trỏ tới ngữ cảnh, ở một mức chi tiết cố định gọi là granularity. Dimension table lưu thuộc tính mô tả ngữ cảnh (ai, cái gì, ở đâu, khi nào). Star schema giữ dimension denormalized (mỗi dimension một bảng rộng) — đơn giản, ít JOIN, query nhanh. Snowflake schema chuẩn hoá dimension thành nhiều bảng liên kết — tiết kiệm chỗ nhưng nhiều JOIN hơn. Warehouse cố ý đi ngược normalization OLTP vì workload của nó là đọc-quét-lớn, không phải ghi-nhiều.
Ở bài 01 ta tách OLAP sang warehouse; bài 02 giải thích warehouse lưu theo cột để quét nhanh. Còn một câu chưa trả lời: dữ liệu trong warehouse được tổ chức thành bảng ra sao?
Người mới thường mặc định "cứ chuẩn hoá đẹp như database giao dịch là chuẩn". Nhưng warehouse làm ngược lại — và đó là một lựa chọn thiết kế có chủ đích, không phải cẩu thả. Bài này giới thiệu dimensional modeling (mô hình hoá theo chiều) — cách tổ chức warehouse quanh fact và dimension — cùng hai biến thể star và snowflake schema, và vì sao denormalize lại hợp OLAP dù bạn vừa học chuẩn hoá ở Module 5.
1. Analogy — Hoá đơn bán hàng và các quyển sổ tra cứu
Hình dung một cửa hàng ghi sổ bán hàng:
- Sổ giao dịch (fact): mỗi dòng là một lần bán — "ngày 12/3, khách số 47, sản phẩm số 200, chi nhánh số 3, bán 5 cái, thu 250 nghìn". Dòng này toàn con số và mã tham chiếu, không ghi tên đầy đủ. Sổ này dài hàng triệu dòng vì mỗi lần bán là một dòng.
- Các quyển sổ tra cứu (dimension): một quyển "danh bạ khách hàng" (số 47 là ai, ở đâu, hạng thành viên gì), một quyển "danh mục sản phẩm" (số 200 là gì, thuộc ngành hàng nào), một quyển "danh sách chi nhánh", một quyển "lịch" (ngày 12/3 thuộc quý mấy). Mỗi quyển ngắn, mô tả ngữ cảnh.
Khi cần báo cáo "doanh thu theo ngành hàng theo quý", ta đọc sổ giao dịch (con số) rồi tra sang các quyển dimension để biết ngành hàng và quý. Sổ giao dịch ở giữa, các quyển tra cứu vây xung quanh — đó chính là hình ngôi sao.
| Cửa hàng | Warehouse |
|---|---|
| Sổ giao dịch — mỗi dòng một lần bán, toàn số + mã | Fact table — số đo (measures) + foreign key |
| Quyển danh bạ khách, danh mục sản phẩm, lịch | Dimension table — thuộc tính mô tả ngữ cảnh |
| Sổ giao dịch dài hàng triệu dòng | Fact table lớn, tăng theo sự kiện |
| Quyển tra cứu ngắn, ít thay đổi | Dimension nhỏ hơn nhiều fact |
| Sổ giữa, quyển tra cứu vây quanh | Star schema — fact ở giữa, dimension xung quanh |
Fact = sổ giao dịch (số đo + mã, hàng triệu dòng). Dimension = quyển tra cứu (mô tả ngữ cảnh, ngắn). Fact ở giữa, dimension vây quanh = ngôi sao.
2. Fact table — sự kiện đo lường được
Fact table là trung tâm của mô hình. Mỗi dòng ghi lại một sự kiện đã xảy ra mà ta muốn phân tích — một lần bán, một task hoàn thành, một lượt xem. Đặc trưng:
- Numeric measures (số đo): các giá trị số có thể cộng/đếm/trung bình — số tiền, số lượng, số giờ. Đây là thứ ta
SUM,AVG,COUNTkhi phân tích. - Foreign key: trỏ tới các dimension table cho ngữ cảnh — "task này của phòng nào", "bán cho khách nào". Fact table hầu như không lưu mô tả dài; nó lưu mã rồi để dimension giải thích mã đó.
- Granularity (độ hạt): mức chi tiết của một dòng fact — quyết định ngay từ đầu và giữ nhất quán. Ví dụ "một dòng = một task hoàn thành" (hạt mịn) khác "một dòng = tổng giờ task mỗi phòng mỗi ngày" (đã gộp sẵn). Granularity sai làm mọi báo cáo sau lệch.
-- Fact table: moi dong la mot task hoan thanh (granularity = 1 task)
CREATE TABLE fact_task_completion (
task_id BIGINT, -- degenerate key (ma su kien)
date_key INT, -- FK -> dim_date
department_key INT, -- FK -> dim_department
assignee_key INT, -- FK -> dim_user
-- numeric measures:
effort_hours NUMERIC, -- so do: cong duoc
reopened_count INT -- so do: dem duoc
);
Vì sao fact toàn số + mã: query phân tích chủ yếu aggregate các số đo và nhóm theo dimension. Tách mô tả ra dimension giúp fact table gọn và hẹp — kết hợp với column store ở bài 02, quét hàng triệu dòng fact rất nhanh vì mỗi dòng ít byte.
3. Dimension table — thuộc tính mô tả
Dimension table lưu ngữ cảnh của sự kiện: ai, cái gì, ở đâu, khi nào. Mỗi dimension trả lời câu hỏi "theo chiều nào" trong báo cáo. Đặc trưng ngược fact:
- Toàn thuộc tính mô tả: tên, nhãn, phân loại, phân cấp — thứ ta đưa vào
GROUP BYvàWHERE(lọc/nhóm "theo phòng ban", "theo quý", "theo hạng thành viên"). - Ít dòng hơn fact nhiều: vài chục phòng ban, vài nghìn sản phẩm — so với hàng triệu dòng fact.
- Khoá chính được fact tham chiếu: mỗi dòng dimension có một surrogate key mà foreign key trong fact trỏ tới.
-- Dimension table: mo ta phong ban (denormalized, rong)
CREATE TABLE dim_department (
department_key INT PRIMARY KEY, -- surrogate key
department_name TEXT, -- "Engineering"
division TEXT, -- "Product" (cap tren, lap lai)
cost_center TEXT,
region TEXT -- "APAC" (cap tren nua)
);
-- Dimension lich: cho phep "theo quy / theo nam" de dang
CREATE TABLE dim_date (
date_key INT PRIMARY KEY, -- 20240312
full_date DATE,
month INT,
quarter INT,
year INT,
is_weekend BOOLEAN
);
Một dimension đặc biệt hữu ích là dim_date (chiều thời gian): thay vì tính quý/tuần từ ngày trong mỗi query, ta tính sẵn một lần và lưu thành cột. Mọi báo cáo "theo quý/tháng/năm" chỉ việc GROUP BY cột có sẵn.
4. Star schema — dimension denormalized, query đơn giản
Star schema (lược đồ ngôi sao) là cách sắp phổ biến nhất: một fact table ở giữa, các dimension table vây xung quanh, mỗi dimension nối trực tiếp tới fact bằng một foreign key. Vẽ ra trông như ngôi sao — fact là tâm, mỗi dimension là một cánh.
Điểm cốt lõi: dimension được giữ denormalized — một bảng rộng, gom mọi thuộc tính kể cả các cấp phân cấp lặp lại. Trong dim_department ở trên, division và region lặp lại cho nhiều phòng cùng division/region — chuẩn hoá OLTP sẽ tách chúng ra bảng riêng, nhưng star schema cố ý giữ chung một bảng.
flowchart TB DIM_DATE["dim_date<br/>(month, quarter, year)"] DIM_DEPT["dim_department<br/>(name, division, region)"] DIM_USER["dim_user<br/>(name, role, seniority)"] DIM_PROJ["dim_project<br/>(name, priority, client)"] FACT["fact_task_completion<br/>measures: effort_hours, reopened_count<br/>FK: date, department, user, project"] DIM_DATE --> FACT DIM_DEPT --> FACT DIM_USER --> FACT DIM_PROJ --> FACT
Vì sao star thắng OLAP về truy vấn:
- Ít JOIN, đơn giản. Mọi báo cáo chỉ là fact JOIN trực tiếp với vài dimension cần dùng — một tầng JOIN, dễ viết, dễ tối ưu. "Doanh thu theo region theo quý" = fact JOIN dim_department JOIN dim_date, hết.
- Dễ hiểu cho người phân tích. Cấu trúc ngôi sao trực quan: chọn số đo ở giữa, chọn chiều ở các cánh.
-- Star schema query: tong gio task theo region theo quy
SELECT d.region, dt.quarter, SUM(f.effort_hours) AS total_hours
FROM fact_task_completion f
JOIN dim_department d ON d.department_key = f.department_key
JOIN dim_date dt ON dt.date_key = f.date_key
GROUP BY d.region, dt.quarter
ORDER BY dt.quarter;
5. Snowflake schema — dimension normalized, tiết kiệm chỗ
Snowflake schema (lược đồ bông tuyết) lấy mỗi dimension rộng của star rồi chuẩn hoá nó thành nhiều bảng nhỏ liên kết — đúng tinh thần normalization ở Module 5. dim_department rộng được tách: bảng department chỉ giữ tên + khoá trỏ tới bảng division, bảng division lại trỏ tới bảng region. Vẽ ra, các cánh sao "nhánh tiếp nhánh" trông như bông tuyết.
flowchart TB REGION["dim_region<br/>(region_name)"] DIVISION["dim_division<br/>(division_name, region_key)"] DEPT["dim_department<br/>(name, division_key)"] FACT["fact_task_completion<br/>measures + FK"] REGION --> DIVISION DIVISION --> DEPT DEPT --> FACT
Đánh đổi star vs snowflake:
- Snowflake tiết kiệm chỗ vì loại bỏ lặp lại:
region"APAC" lưu một lần ởdim_regionthay vì lặp trong mọi dòng phòng ban. Cập nhật cũng gọn — đổi tên một region sửa một chỗ. - Nhưng snowflake nhiều JOIN hơn. Báo cáo "theo region" giờ phải JOIN qua chuỗi
fact → department → division → region— nhiều tầng hơn, query phức tạp hơn, và thường chậm hơn vì JOIN là phép tốn của OLAP. - Star đơn giản hơn, query nhanh hơn, đổi lại tốn chỗ hơn do lặp.
Với warehouse hiện đại — đĩa rẻ, dữ liệu nén tốt (bài 02), và mục tiêu là query nhanh + dễ viết — star schema thường được ưu tiên. Snowflake hợp khi dimension rất lớn hoặc phân cấp thay đổi thường xuyên, lúc đó tiết kiệm chỗ và dễ bảo trì phân cấp mới đáng đánh đổi thêm JOIN.
| Tiêu chí | Star schema | Snowflake schema |
|---|---|---|
| Dimension | Denormalized (rộng, một bảng) | Normalized (nhiều bảng liên kết) |
| Số JOIN mỗi query | Ít (một tầng) | Nhiều (qua chuỗi phân cấp) |
| Tốc độ query | Nhanh hơn | Chậm hơn (nhiều JOIN) |
| Dung lượng | Tốn hơn (lặp lại) | Tiết kiệm hơn |
| Độ phức tạp viết query | Đơn giản | Phức tạp hơn |
| Khi nào ưu tiên | Mặc định OLAP | Dimension lớn/phân cấp hay đổi |
6. Vì sao denormalize hợp OLAP — ngược normalization OLTP
Ở Module 5 bạn học chuẩn hoá: tách dữ liệu để loại bỏ lặp lại, tránh anomaly khi cập nhật, giữ tính nhất quán khi ghi. Đó là tối ưu đúng cho OLTP — workload ghi nhiều, nơi một sự thật bị lặp ở nhiều chỗ dễ lệch khi sửa.
Warehouse OLAP có workload ngược hẳn: ghi theo lô định kỳ (ETL/ELT), gần như chỉ-đọc sau đó, và đọc là quét + JOIN khối lượng lớn. Trong bối cảnh này:
- Lặp lại không còn là rủi ro lớn vì dữ liệu hiếm khi sửa từng dòng — pipeline nạp lại theo lô, không phải hàng nghìn
UPDATElẻ như OLTP. Cái mà normalization phòng chống (update anomaly) gần như không xảy ra. - JOIN là chi phí đáng kể khi quét triệu dòng. Denormalize (gộp thuộc tính vào dimension rộng) giảm số JOIN mỗi báo cáo — đổi một ít dung lượng lấy tốc độ và sự đơn giản.
Nói cách khác, normalization và dimensional modeling không mâu thuẫn — chúng tối ưu cho hai workload khác nhau. OLTP chuẩn hoá để ghi an toàn; OLAP denormalize để đọc nhanh. Cùng một sự thật, hai cách tổ chức, vì hai mục tiêu.
Mô hình hoá theo chiều (fact + dimension, star/snowflake schema) gắn liền với Ralph Kimball — người hệ thống hoá cách thiết kế warehouse quanh sự kiện đo lường được. Trong cách tiếp cận này: fact table giữ numeric measures + foreign key; dimension table giữ thuộc tính mô tả; star schema giữ dimension denormalized (rộng), còn snowflake chuẩn hoá dimension thành nhiều bảng liên kết.
7. Pitfall — sai granularity và chuẩn hoá quá tay
Hai lỗi thiết kế warehouse hay gặp:
-
Chọn sai granularity fact table. Nếu trộn nhiều mức chi tiết trong một fact (vài dòng là từng task, vài dòng đã gộp theo ngày), mọi
SUMsẽ đếm trùng hoặc thiếu. Quy tắc: chốt granularity rõ từ đầu ("một dòng = một task hoàn thành"), giữ nhất quán toàn bảng, và đặt granularity mịn nhất hợp lý — vì từ hạt mịn luôn roll-up lên hạt thô được, ngược lại thì không. -
Bê nguyên tư duy chuẩn hoá OLTP vào warehouse. Chuẩn hoá mọi dimension thành snowflake "cho đẹp" làm mọi báo cáo phải JOIN qua nhiều tầng — chậm và khó viết, mà lợi ích (tránh update anomaly) gần như vô nghĩa với dữ liệu chỉ-đọc nạp theo lô.
SAI: fact tron granularity
dong A: 1 task = 1 dong
dong B: tong theo ngay = 1 dong -> SUM dem trung, bao cao lech
SAI: chuan hoa het dimension thanh snowflake "cho dep"
-> moi bao cao JOIN 4-5 tang -> cham, kho viet, loi it
DUNG:
- granularity nhat quan + min nhat hop ly (roll-up sau)
- mac dinh star (denormalize); chi snowflake khi that su can
Quy tắc: warehouse không phải database giao dịch thu nhỏ — đừng áp nguyên tắc thiết kế OLTP lên nó.
8. 📚 Deep Dive
- Designing Data-Intensive Applications (Kleppmann) — Chương 3 "Storage and Retrieval", mục "Stars and Snowflakes: Schemas for Analytics" — nguồn nền tảng, agnostic: giải thích fact/dimension table và vì sao star/snowflake schema hợp warehouse.
- Wikipedia — Star schema — định nghĩa star schema, fact vs dimension, đối chiếu với snowflake schema.
Ghi chú: Dimensional modeling (fact + dimension, star/snowflake) gắn với Ralph Kimball. DDIA Chương 3 trình bày khái niệm này một cách agnostic — đọc mục "Stars and Snowflakes" để hiểu vì sao denormalize hợp phân tích. Wikipedia "Star schema" cho định nghĩa chuẩn và so sánh với snowflake.
9. Liên hệ các bài khác
- Bài 01 — OLTP vs OLAP: vì sao tách warehouse ra khỏi database giao dịch — bối cảnh cho việc warehouse có cách tổ chức bảng riêng.
- Bài 02 — Row store vs column store: fact table hẹp (số + mã) kết hợp column store làm quét triệu dòng fact rất nhanh — hai quyết định thiết kế bổ trợ nhau.
- Module 5 — Normalization 1NF–3NF: warehouse cố ý đi ngược chuẩn hoá; đọc bài này để thấy rõ vì sao OLTP chuẩn hoá còn OLAP denormalize — hai workload, hai mục tiêu.
- Module 6 — Vì sao cần index: index tối ưu tra theo khoá (OLTP); dimensional modeling tối ưu nhóm-theo-chiều (OLAP) — hai hướng tối ưu cho hai workload.
10. Tóm tắt
- Warehouse OLAP không chuẩn hoá như OLTP mà tổ chức quanh fact (sự kiện đo lường) và dimension (ngữ cảnh mô tả).
- Fact table: numeric measures + foreign key, ở một granularity cố định; gọn và hẹp, hàng triệu dòng.
- Dimension table: thuộc tính mô tả (tên, phân loại, phân cấp) để
GROUP BY/WHERE; ít dòng hơn fact nhiều. - Star schema: dimension denormalized (rộng), ít JOIN, query đơn giản và nhanh — mặc định cho OLAP.
- Snowflake schema: chuẩn hoá dimension thành nhiều bảng — tiết kiệm chỗ nhưng nhiều JOIN hơn, query phức tạp hơn.
- Denormalize hợp OLAP vì workload chỉ-đọc nạp theo lô: lặp lại ít rủi ro, còn giảm JOIN thì tăng tốc — ngược mục tiêu ghi-an-toàn của normalization OLTP.
- Pitfall: sai/trộn granularity làm báo cáo lệch; over-normalize warehouse làm query chậm mà lợi ích không đáng.
11. Tự kiểm tra
Q1Phân biệt fact table và dimension table qua nội dung lưu trữ và vai trò trong báo cáo.▸
Fact table lưu các sự kiện đo lường được: mỗi dòng là một lần xảy ra (một task hoàn thành, một lần bán), gồm numeric measures (số tiền, số giờ — thứ ta SUM/AVG) và foreign key trỏ tới dimension. Nó hẹp, toàn số + mã, và rất nhiều dòng.
Dimension table lưu thuộc tính mô tả ngữ cảnh (tên phòng, phân loại sản phẩm, quý/năm) — thứ ta đưa vào GROUP BY và WHERE để nhóm/lọc "theo chiều nào". Nó ít dòng hơn fact nhiều. Trong báo cáo: fact cho con số cần tính, dimension cho cách cắt lát con số đó.
Q2Granularity của fact table là gì, và vì sao chọn sai hoặc trộn nhiều granularity lại nguy hiểm?▸
Granularity là mức chi tiết của một dòng fact — ví dụ "một dòng = một task hoàn thành" (mịn) khác "một dòng = tổng giờ mỗi phòng mỗi ngày" (đã gộp). Nó phải được chốt từ đầu và giữ nhất quán toàn bảng.
Trộn nhiều granularity (vài dòng là từng task, vài dòng đã gộp) làm SUM đếm trùng hoặc thiếu — mọi báo cáo sau đó lệch. Nên chọn granularity mịn nhất hợp lý vì từ hạt mịn luôn roll-up lên hạt thô được, còn từ hạt thô thì không tách ngược ra chi tiết được.
Q3So sánh star và snowflake schema: khác nhau ở đâu và đánh đổi là gì?▸
- Star: dimension denormalized (mỗi dimension một bảng rộng, gom cả phân cấp lặp lại). Ít JOIN (một tầng), query đơn giản và nhanh; đổi lại tốn chỗ hơn do lặp.
- Snowflake: dimension được chuẩn hoá thành nhiều bảng nhỏ liên kết theo phân cấp. Tiết kiệm chỗ và dễ bảo trì phân cấp; đổi lại nhiều JOIN hơn (qua chuỗi bảng), query phức tạp và thường chậm hơn.
Với warehouse hiện đại (đĩa rẻ, nén tốt, ưu tiên query nhanh), star thường là mặc định; snowflake chỉ đáng khi dimension rất lớn hoặc phân cấp hay thay đổi.
Q4Bạn vừa học ở Module 5 rằng nên chuẩn hoá để tránh lặp lại. Vì sao warehouse lại cố ý denormalize — có mâu thuẫn không?▸
Không mâu thuẫn — hai bên tối ưu cho hai workload khác nhau. Normalization hợp OLTP vì OLTP ghi nhiều: một sự thật lặp ở nhiều chỗ dễ lệch khi sửa (update anomaly), nên tách ra để ghi an toàn.
Warehouse OLAP ngược lại: ghi theo lô định kỳ rồi gần như chỉ-đọc, và đọc là quét + JOIN khối lượng lớn. Ở đây lặp lại ít rủi ro (hiếm khi sửa từng dòng), trong khi JOIN là chi phí đáng kể. Denormalize giảm JOIN để đọc nhanh — đổi một ít dung lượng lấy tốc độ. Cùng một sự thật, hai cách tổ chức, vì hai mục tiêu (ghi-an-toàn vs đọc-nhanh).
Q5Vì sao fact table được thiết kế hẹp (chỉ số đo + mã, không lưu mô tả dài), và điều này phối hợp thế nào với column store ở bài 02?▸
Fact hẹp vì query phân tích chủ yếu aggregate các số đo rồi nhóm theo dimension — bản thân fact không cần mô tả dài, chỉ cần con số và mã trỏ sang dimension để giải thích. Tách mô tả ra dimension giữ mỗi dòng fact ít byte.
Phối với column store (bài 02): fact hẹp + lưu theo cột nghĩa là quét một số đo trên hàng triệu dòng chỉ đọc đúng cột số đó, lại nén tốt vì giá trị cùng cột đồng kiểu. Hai quyết định bổ trợ nhau — mô hình hoá dữ liệu gọn và mô hình lưu trữ theo cột cùng hướng tới một mục tiêu: quét khối lượng lớn thật nhanh.
Bài tiếp theo: ETL, ELT, data lake & lakehouse
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên