Analytics, BI & Data ServingAnalytics, BI & Data Serving
Thuộc bộ kiến thức Data Engineer Roadmap.
Tổng quan
Từ đầu đến giờ, các bài trong bộ kiến thức này đều xoay quanh việc đưa dữ liệu vào một nơi được tổ chức tốt: ingest dữ liệu (./03-data-sources-and-ingestion.md), modeling vào warehouse (./06-data-modeling-and-warehousing.md), và di chuyển qua các pipeline (./08-etl-elt-and-data-pipelines.md). Tất cả công sức đó chỉ thực sự có giá trị khi có ai đó — hoặc hệ thống nào đó — thật sự tiêu thụ (consume) kết quả. Serving layer (tầng phục vụ dữ liệu) là giai đoạn cuối cùng của data engineering lifecycle: nơi dữ liệu đã được modeling, đáng tin cậy, được expose ra cho con người thông qua dashboard và các công cụ self-service BI, cho hệ thống khác thông qua API và reverse ETL, và cho các mô hình machine learning thông qua feature store. Công việc của một data engineer chưa hoàn thành khi một dbt run chạy thành công; nó chỉ hoàn thành khi một nhân viên sales nhìn thấy đúng con số trong Salesforce, một lãnh đạo tin tưởng biểu đồ revenue trên dashboard, hay một model fraud detection chấm điểm một giao dịch theo thời gian thực bằng các feature được tính đúng.
Bài này cố tình tập trung vào “chặng cuối cùng” (last mile). Nó không giải thích lại cách modeling warehouse hay xây pipeline — nó giải thích điều xảy ra sau khi công việc đó hoàn tất: các công cụ BI như Tableau, Looker, Power BI biến một warehouse được modeling tốt thành dashboard nhanh và đáng tin cậy như thế nào; tại sao cần một semantic layer để ngăn năm dashboard hiển thị năm con số khác nhau cho “revenue”; các công cụ nhẹ như Streamlit nằm ở đâu so với các nền tảng BI đầy đủ; data engineer giúp analyst phân tích sâu hơn mà không tự làm phân tích đó như thế nào; kỷ luật kỹ thuật đứng sau việc A/B testing đáng tin cậy; và vấn đề đặc thù của serving layer — training-serving skew — mà feature store được sinh ra để giải quyết.
Kiến thức nền tảng
Serving layer — giai đoạn cuối của lifecycle
Có thể hình dung data engineering lifecycle kết thúc bằng một điểm phân nhánh (fan-out) chứ không phải một đích đến duy nhất. Sau khi dữ liệu được extract, lưu trữ, và transform thành các bảng modeling sạch, nó tỏa ra bất cứ nơi nào tạo ra giá trị:
- con người khám phá trực quanBI toolTableau, Looker, Power BI
- các công cụ SaaS vận hànhReverse ETL
- ML training & inferenceFeature store
- analyst, data scientistAd-hoc SQL / notebook
Mỗi consumption pattern này có yêu cầu khác nhau về độ tươi (freshness), độ trễ (latency), và hình dạng truy vấn (query shape), và mỗi cái sẽ hỏng theo một cách riêng nếu data engineer coi “model đã build xong” là vạch đích. Một mart hoàn hảo cho việc refresh BI hằng đêm có thể quá chậm, hoặc quá cũ, để phục vụ một feature store cho inference thời gian thực. Thiết kế serving layer tốt nghĩa là hiểu, với từng consumer, không chỉ dữ liệu gì họ cần mà còn nhanh đến đâu, tần suất ra sao, và hình dạng nào — và sự hiểu biết đó phải có trước khi pipeline được xây, chứ không phải sau khi phát hiện dashboard chậm đến mức không dùng được.
Business Intelligence (BI) tools
Các công cụ BI tồn tại để cho phép những người không viết SQL — hoặc không muốn viết SQL mỗi lần — khám phá dữ liệu trực quan và xây dashboard mà không cần đội engineering hỗ trợ. Ba cái tên thống trị đều đi từ cùng một ý tưởng nền tảng (kết nối tới warehouse, modeling hoặc map các bảng của nó, cho người dùng kéo-thả field lên canvas để tạo chart) nhưng khác nhau về triết lý:
| Công cụ | Mô hình hoạt động | Điểm mạnh nổi bật |
|---|---|---|
| Tableau | Kết nối tới warehouse hoặc extract dữ liệu vào engine in-memory riêng (Hyper) | Khám phá trực quan và phân tích ad-hoc “kéo-thả” hàng đầu; phù hợp cho analyst muốn tự do đào sâu dữ liệu |
| Looker | Query-pushdown — luôn query trực tiếp warehouse, không bao giờ extract dữ liệu ra riêng | LookML, một semantic modeling layer được version-control (xem bên dưới) tập trung hóa định nghĩa metric và dimension |
| Power BI | Kết nối tới warehouse hoặc import dữ liệu vào engine columnar riêng (VertiPaq) | Tích hợp sâu với hệ sinh thái Microsoft (Excel, Azure, Teams); DAX cho các measure tính toán |
Điểm chung của cả ba là chúng chỉ tốt bằng chất lượng dữ liệu bên dưới. Một BI tool là một thấu kính, không phải một cách sửa lỗi — nó hiển thị bất cứ thứ gì warehouse cung cấp, với tốc độ mà warehouse có thể trả lời một query. Đây là lý do bài về data modeling trước đó liên quan trực tiếp đến đây: một warehouse được modeling tốt — cụ thể là star schema (./06-data-modeling-and-warehousing.md) với các bảng fact và dimension sạch sẽ — chính là thứ khiến BI tool cảm giác nhanh và đáng tin cậy, còn một warehouse đầy các bảng raw không được quản trị và join tùy tiện là thứ khiến chúng cảm giác chậm và rối rắm.
Hai cơ chế cụ thể giải thích tại sao model lại quan trọng đến vậy:
- Tốc độ query. Một BI tool thường generate SQL ngay khi người dùng kéo field lên canvas. Nếu các bảng bên dưới đã là star schema denormalized — chỉ một join từ fact tới mỗi dimension — SQL được generate ra chỉ là vài join và aggregation đơn giản, và một warehouse columnar hiện đại trả lời trong vài giây. Nếu thay vào đó BI tool phải join tám bảng normalized và phải phân biệt cột
created_atnào trong ba cột là “đúng”, mỗi lần load dashboard trở thành một query đắt đỏ, mong manh, và người dùng cảm nhận điều đó là “BI chậm”. - Độ đáng tin cậy. Một star schema với các fact và dimension được đặt tên rõ ràng, tài liệu hóa đầy đủ (
fct_orders,dim_customer) cho người dùng một bộ từ vựng nhỏ, dễ hiểu để xây dựng lên. Một đống bảng raw, không tài liệu sẽ khiến mỗi analyst định nghĩa “một đơn hàng” hay “một khách hàng active” hơi khác nhau một chút, và đó chính là mầm mống của vấn đề mà semantic layer (phần tiếp theo) được sinh ra để ngăn chặn.
Khám phá self-service và giới hạn của nó
Lời hứa của BI tool là self-service: một trưởng nhóm marketing tự xây biểu đồ hiệu suất chiến dịch của mình mà không cần mở ticket cho đội dữ liệu. Điều này thực sự hiệu quả khi warehouse được modeling tốt, bởi self-service exploration thực chất là “để người không phải kỹ sư tự kết hợp các khối xây dựng (fact, dimension, metric) đã được định nghĩa sẵn và đáng tin cậy”, chứ không phải “để bất kỳ ai viết SQL tùy ý trên dữ liệu raw”. Công việc của data engineer là xây và duy trì những khối xây dựng đó; BI tool và semantic layer là thứ khiến việc kết hợp chúng an toàn cho những người không có nền tảng data engineering.
Khái niệm chính
Semantic layer: một metric, định nghĩa một lần duy nhất
Ý tưởng quan trọng nhất trong bài này là semantic layer: một tầng được quản trị (governed), nằm giữa warehouse và mọi BI tool hay dashboard, nơi các metric và dimension nghiệp vụ được định nghĩa đúng một lần — “active user”, “revenue”, “churned customer” — và mọi consumer phía sau tham chiếu tới định nghĩa duy nhất đó thay vì tự tính lại.
Vấn đề mà semantic layer giải quyết rất quen thuộc với bất kỳ ai từng làm ở công ty có nhiều hơn một dashboard: năm dashboard khác nhau hiển thị năm con số khác nhau cho “revenue”, vì năm người khác nhau viết năm câu SQL khác nhau, mỗi câu mang một giả định hơi khác. “Revenue” của analyst này bao gồm thuế; của người khác thì loại trừ refund; người thứ ba join theo order_date trong khi người thứ tư join theo shipped_date. Không ai trong số họ nói dối — mỗi người chỉ đưa ra một lựa chọn nghe có vẻ hợp lý một cách độc lập, và không có một nơi duy nhất để tra cứu định nghĩa “chính thức”. Kết quả là một cuộc khủng hoảng niềm tin: ban lãnh đạo ngừng tin dashboard, và mỗi con số lại bị tranh cãi lại trong các cuộc họp thay vì thúc đẩy quyết định.
Semantic layer sửa vấn đề này về mặt cấu trúc chứ không dựa vào kỷ luật hay email nhắc nhở. Metric được định nghĩa một lần, thường ở dạng code:
# Ví dụ: một metric định nghĩa một lần trong semantic layer (kiểu dbt Semantic Layer / MetricFlow)
metrics:
- name: revenue
label: "Revenue"
description: "Doanh thu thuần ghi nhận: tổng giá trị đơn hàng trừ refund, không tính thuế."
type: simple
type_params:
measure: net_order_amount
filter: |
{{ Dimension('order__status') }} = 'completed'
# Ví dụ: cùng ý tưởng được thể hiện bằng LookML, semantic modeling layer của Looker
measure: revenue {
type: sum
sql: ${TABLE}.net_order_amount ;;
filters: [order.status: "completed"]
description: "Doanh thu thuần ghi nhận: tổng giá trị đơn hàng trừ refund, không tính thuế."
}
Mọi dashboard, mọi ad-hoc query đi qua semantic layer, và mọi BI tool kết nối tới nó giờ đây kế thừa cùng một revenue — thay đổi định nghĩa một lần (chẳng hạn bộ phận tài chính quyết định revenue nên được ghi nhận theo shipped_date thay vì order_date) và mọi consumer cập nhật đồng bộ, thay vì phải lùng sục và sửa mười hai câu SQL riêng lẻ nằm rải rác trong các dashboard mà không ai còn nhớ ai đã xây.
LookML đáng được nhắc riêng ở đây vì đây là một trong những triển khai được áp dụng rộng rãi sớm nhất của ý tưởng này: thay vì mỗi dashboard Looker chứa SQL riêng, LookML định nghĩa dimension và measure một lần, trong các file .lkml được version-control, và mọi “Explore” cũng như dashboard trong Looker được sinh ra từ model chung đó. dbt Semantic Layer gần đây hơn (xây trên nền MetricFlow) tổng quát hóa cùng ý tưởng đó ra ngoài phạm vi của bất kỳ một BI tool cụ thể nào — các metric được định nghĩa một lần trong dbt project cùng với các model mà chúng được xây dựng từ đó, và bất kỳ BI tool nào có tích hợp semantic layer (không riêng của một vendor) đều có thể query chúng một cách nhất quán. Công cụ cụ thể không quan trọng bằng nguyên tắc: định nghĩa metric thuộc về một tầng được quản trị, version-control, chứ không phải rải rác trong SQL riêng của từng dashboard.
Streamlit và các internal app nhẹ
Không phải nhu cầu dữ liệu nội bộ nào cũng đáng để chịu chi phí — về license, setup, và overhead quản trị — của một nền tảng BI đầy đủ. Streamlit (và các framework tương tự như Gradio hay Dash) cho phép data engineer hoặc analyst biến một script Python thành một web app tương tác chỉ trong vài chục dòng code, mà không cần đụng tới HTML, CSS, hay JavaScript:
import streamlit as st
import pandas as pd
df = pd.read_sql("select * from fct_orders", con=warehouse_connection)
st.title("Orders Dashboard")
region = st.selectbox("Region", df["region"].unique())
filtered = df[df["region"] == region]
st.metric("Total Revenue", f"${filtered['amount_usd'].sum():,.2f}")
st.bar_chart(filtered.groupby("order_month")["amount_usd"].sum())
Vị trí ngách của nó so với Tableau, Looker, hay Power BI hẹp nhưng có thật: các công cụ nội bộ nhanh gọn cho một nhóm người dùng nhỏ — một app khám phá dùng một lần cho đội data science, một công cụ QA nội bộ để review kết quả pipeline, một dashboard prototype trước khi quyết định một metric có xứng đáng có một “ngôi nhà” lâu dài trong stack BI thật sự hay không. Điều nó cố tình không cung cấp là chính điều mà một nền tảng BI đầy đủ được xây dựng để làm: governance cấp doanh nghiệp, row-level security, semantic layer dùng chung, phân phối theo lịch cho hàng trăm người dùng nghiệp vụ, hay trải nghiệm self-service bóng bẩy cho nhân viên không rành kỹ thuật. Quy tắc chung: dùng Streamlit khi đối tượng nhỏ, kỹ thuật, và nội bộ; dùng nền tảng BI khi đối tượng rộng, không rành kỹ thuật, và cần các metric khớp với mọi dashboard khác trong công ty.
Thực hành data analytics: analyst làm gì với những gì engineer xây
Đáng để làm rõ sự phân công lao động ngụ ý trong tất cả những gì ở trên. Descriptive analytics (mô tả — chuyện gì đã xảy ra) phần lớn là công việc của BI tool: nó trả lời các câu hỏi như “revenue quý trước là bao nhiêu” hay “khu vực nào tăng trưởng nhanh nhất”, và đây chính xác là loại câu hỏi mà một warehouse được modeling tốt cùng semantic layer được xây dựng để trả lời nhanh và nhất quán. Xa hơn nữa là phân tích sâu hơn: diagnostic analytics (tại sao chuyện đó xảy ra), và những công việc pha trộn vào data science — phân tích cohort, kiểm định thống kê, suy luận nhân quả (causal inference), dự báo (forecasting).
Vai trò của data engineer trong công việc sâu hơn này là hỗ trợ (enabling), không phải thực hiện. Data engineer thường không chạy hồi quy hay thiết kế phân tích cohort; data engineer đảm bảo pipeline nuôi phân tích đó tạo ra các data model sạch, tài liệu hóa đầy đủ, hiệu năng tốt, để analyst hoặc data scientist có thể dành thời gian cho câu hỏi phân tích thực sự thay vì cho việc “khảo cổ dữ liệu” — truy tìm tại sao một join tạo ra hàng trùng lặp, hay tại sao ý nghĩa của một cột thay đổi âm thầm ba tháng trước. Dấu hiệu rõ ràng nhất của một bàn giao lành mạnh là analyst dành phần lớn thời gian đặt và trả lời câu hỏi nghiệp vụ, và chỉ hiếm khi phải đào sâu vào bảng nguồn raw vì có gì đó trong tầng modeling không khớp.
Hạ tầng A/B testing
Thử nghiệm (experimentation) là một trong những ví dụ sắc nét nhất về việc “data engineering vẫn quan trọng dù câu hỏi thú vị là một câu hỏi thống kê”. Một A/B test chỉ đáng tin cậy bằng chính các event nuôi nó, và có một số điểm hỏng đặc thù kỹ thuật có thể âm thầm vô hiệu hóa một thí nghiệm được thiết kế tốt về mặt thống kê:
- Ghi log assignment đáng tin cậy. Khi một người dùng được phân vào một variant, event assignment đó phải được ghi log đáng tin cậy và đúng một lần — ghi quá trễ, bị mất khi tải cao, hoặc ghi nhiều lần, và mẫu số của thí nghiệm (bao nhiêu người dùng thực sự tiếp xúc với mỗi variant) đã sai ngay cả trước khi xem xét đến dữ liệu kết quả.
- Ghi log outcome đáng tin cậy và join đúng. Event outcome (một giao dịch mua, một cú click, một lượt đăng ký) phải được join lại đúng với assignment tương ứng — bằng một định danh user hoặc session ổn định, trong đúng khung thời gian tương ứng với lúc tiếp xúc. Một bug pipeline join outcome sai với assignment (ví dụ dùng session ID reset qua các thiết bị, hoặc một khung thời gian để lọt hành vi trước-thí-nghiệm vào phân tích) có thể khiến một tính năng trung tính trông như thắng, hoặc một tính năng thắng trông như trung tính.
- Sample Ratio Mismatch (SRM). Nếu một thí nghiệm được cấu hình chia 50/50 nhưng dữ liệu assignment ghi lại cho thấy, chẳng hạn, tỷ lệ 52/48 có ý nghĩa thống kê, đó là sample ratio mismatch — một tín hiệu mạnh cho thấy có gì đó trong pipeline bị hỏng (một bug khiến event assignment của một variant bị rớt thường xuyên hơn, một lớp redirect hay cache hoạt động khác nhau theo variant, một rule lọc bot loại bỏ không cân đối một nhóm) chứ không phải một vấn đề ngẫu nhiên hóa thực sự. Kiểm tra SRM nên chạy tự động trước khi bất kỳ ai xem kết quả metric thực tế, bởi một thí nghiệm có SRM không đo được đúng cái nó tuyên bố đo, dù con số cuối cùng có thuyết phục đến đâu.
Yêu cầu kỹ thuật ở đây hẹp nhưng không thể thương lượng: một event pipeline (./08-etl-elt-and-data-pipelines.md, ./11-streaming-and-realtime-data.md) ghi nhận event assignment và outcome với đảm bảo delivery mạnh, định danh ổn định tồn tại suốt thời gian thí nghiệm, và giám sát tự động phát hiện SRM cùng các bất thường pipeline khác — bởi một nhà thống kê ở cuối chuỗi không thể sửa được dữ liệu thiếu hoặc join sai, dù bài kiểm định ý nghĩa có tinh vi đến đâu.
Feature store và training-serving skew
Việc tiêu thụ serving layer cho machine learning có một vấn đề mà BI dashboard không có: cùng một feature — chẳng hạn “số lần mua hàng của khách này trong 30 ngày qua” — phải được tính giống hệt nhau trong hai bối cảnh rất khác nhau. Trong quá trình training, nó được tính hàng loạt trên dữ liệu lịch sử, thường bằng một job SQL hoặc Spark batch. Trong quá trình inference (phục vụ dự đoán thời gian thực), nó phải được tính cho một entity duy nhất, ngay lúc này, thường với ngân sách độ trễ tính bằng mili giây, đôi khi từ một code path hoàn toàn khác hoặc thậm chí một service của một đội khác. Nếu hai phép tính này lệch nhau dù chỉ một chút — xử lý múi giờ khác nhau, lệch một đơn vị trong cửa sổ lookback, định nghĩa “giao dịch mua” hơi khác — model sẽ thấy giá trị feature khác trong production so với lúc nó được huấn luyện, và độ chính xác của nó âm thầm giảm sút. Đây là training-serving skew, và đây là một trong những nguyên nhân phổ biến nhất khiến một model chấm điểm tốt offline nhưng lại kém trong production.
Một feature store (Feast, Tecton, hoặc dịch vụ native trên cloud như SageMaker Feature Store) là serving layer chuyên biệt được xây để đóng khoảng cách này. Nó tính một feature một lần, từ một định nghĩa transformation duy nhất, và lưu trữ dưới hai dạng được giữ nhất quán với nhau: một offline store (thường là warehouse hoặc lake, chứa giá trị feature lịch sử để training) và một online store (một key-value store độ trễ thấp, thường dựa trên Redis, chứa giá trị hiện tại của mỗi feature để tra cứu inference thời gian thực). Vì cả hai store đều được nạp từ cùng một định nghĩa feature, một model được huấn luyện với giá trị lịch sử của offline store sẽ thấy đúng ngữ nghĩa feature đó tại thời điểm inference từ online store — loại bỏ skew bằng thiết kế thay vì bằng quy ước.
Vai trò của data engineer trong ML pipeline phản ánh vai trò của họ trong analytics: tính toán feature đáng tin cậy, không phải huấn luyện model. Xây dựng và vận hành các pipeline tính feature đúng, backfill giá trị lịch sử cho training, và giữ online store luôn cập nhật (thường theo cơ chế streaming cho các feature cần phản ánh hoạt động rất gần đây) chính là công việc data engineering; chọn kiến trúc model và tinh chỉnh nó là công việc thuộc về vai trò machine learning. Phần nội dung ML/MLOps sâu hơn — huấn luyện model, đánh giá, triển khai — nếu có ở nơi khác trong bộ kiến thức này, thì bài này chỉ giới hạn trong nửa serving layer của ranh giới đó.
Các mẫu tiêu thụ và điểm hỏng thường gặp
Các consumer khác nhau của serving layer cần những thứ khác nhau từ data engineer, và việc bỏ bê serving layer sẽ làm hỏng mỗi cái theo một cách đặc trưng:
| Consumption pattern | Data engineer cần cung cấp gì | Điểm hỏng thường gặp nếu bị bỏ bê |
|---|---|---|
| BI dashboard | Star schema được modeling tốt, hiệu năng query nhanh, metric được định nghĩa trong semantic layer dùng chung | Dashboard chậm, hoặc năm dashboard mâu thuẫn về cùng một metric vì mỗi cái được xây bằng SQL ad-hoc |
| Reverse ETL | Bảng mart đáng tin cậy, đã khử trùng lặp, với key ổn định khớp với dữ liệu ở hệ thống đích | Dữ liệu sai hoặc cũ bị đẩy vào CRM/công cụ marketing, âm thầm làm hỏng hệ thống vận hành mà đội nghiệp vụ dựa vào |
| ML feature store | Feature được tính giống hệt cho training (offline) và inference (online), refresh theo nhịp/độ trễ đã biết | Training-serving skew — model suy giảm hiệu năng trong production dù chấm điểm tốt offline, và nguyên nhân vô hình nếu không so sánh trực tiếp giá trị feature offline và online |
| Ad-hoc analyst query | Các bảng sạch, tài liệu hóa, hiệu năng tốt, đặt tên và lineage rõ ràng | Analyst tốn thời gian đảo ngược các bảng raw, hoặc rút ra kết luận sai từ cột/join không được tài liệu hóa |
Best Practices
- Coi semantic layer là hạ tầng bắt buộc, không phải “có thì tốt”. Ngay khi hơn một dashboard hoặc công cụ cần cùng một metric, hãy định nghĩa nó một lần trong một tầng được quản trị (LookML, dbt Semantic Layer, hoặc tương đương) thay vì để nó bị tính lại tùy tiện — vấn đề “năm dashboard, năm con số” rẻ hơn nhiều để ngăn chặn so với việc debug sau khi ban lãnh đạo đã mất niềm tin vào dữ liệu.
- Modeling theo consumer, không chỉ theo lưu trữ. Một star schema tối ưu cho tốc độ query BI có thể không phải hình dạng đúng cho việc tra cứu độ trễ thấp của feature store; thiết kế hình dạng serving xoay quanh cách dữ liệu thực sự được truy vấn, không chỉ xoay quanh cách lưu trữ rẻ nhất.
- Đừng bao giờ để BI tool che đậy một model tồi. Nếu một dashboard chậm hoặc rối, cách sửa hầu như luôn nằm ở thượng nguồn — modeling tốt hơn, join tốt hơn, tài liệu tốt hơn — chứ không phải một cách vá phức tạp hơn trong BI tool đặt chồng lên một warehouse lộn xộn.
- Trang bị cho pipeline A/B testing các kiểm tra SRM và delivery event tự động, và coi những kiểm tra đó là một cổng chạy trước khi bất kỳ ai xem kết quả thí nghiệm, vì một pipeline hỏng có thể tạo ra một câu trả lời tự tin nhưng sai, trông có vẻ hợp lệ về mặt thống kê.
- Xây dựng logic tính feature một lần và dùng chung giữa training và serving, thông qua một feature store hoặc một transformation dùng chung tương đương, thay vì để pipeline training và service inference tự triển khai lại độc lập cùng một logic feature — sự trùng lặp đó chính là nơi training-serving skew len lỏi vào.
- Dùng công cụ nhẹ (Streamlit) cho đối tượng nội bộ, kỹ thuật, quy mô nhỏ, và dùng nền tảng BI đầy đủ khi đối tượng rộng hoặc không rành kỹ thuật — khớp đúng công cụ với đối tượng giúp tránh cả việc over-engineer một script nội bộ dùng một lần lẫn việc phục vụ kém một nhu cầu báo cáo toàn công ty.
- Nhớ rằng “xong” nghĩa là “được tiêu thụ”, không phải “đã được modeling”. Một pipeline đưa dữ liệu hoàn hảo vào một mart không ai query, một feature không model nào dùng, hay một metric không dashboard nào tham chiếu thì chưa mang lại giá trị — khép lại vòng lặp với consumer thực sự, như được đề cập thêm ở
./20-career-and-best-practices.md, là một phần của công việc, không phải một điểm cộng thêm.
Tài liệu tham khảo
Part of the Data Engineer Roadmap knowledge base.
Overview
Every note so far in this knowledge base has been about getting data into a well-shaped place: ingesting it (./03-data-sources-and-ingestion.md), modeling it into a warehouse (./06-data-modeling-and-warehousing.md), and moving it through pipelines (./08-etl-elt-and-data-pipelines.md). None of that work pays off until someone — or something — actually consumes the result. The serving layer is the final stage of the data engineering lifecycle: the point where modeled, trustworthy data is exposed to humans through dashboards and self-service BI tools, to other systems through APIs and reverse ETL, and to machine learning models through feature stores. A data engineer’s job is not complete when a dbt run succeeds; it is complete when a sales rep sees the right number in Salesforce, an executive trusts the revenue chart in a dashboard, or a fraud model scores a transaction in real time using features that were computed correctly.
This note is deliberately “last mile” in focus. It does not re-explain how to model a warehouse or build a pipeline — it explains what happens after that work is done: how BI tools like Tableau, Looker, and Power BI turn a well-modeled warehouse into fast, trustworthy dashboards; why a semantic layer is necessary to stop five dashboards from showing five different numbers for “revenue”; where lightweight tools like Streamlit fit relative to full BI platforms; how data engineers enable analysts to do deeper analysis without doing that analysis themselves; the data engineering discipline behind reliable A/B testing; and the specific serving-layer problem — training-serving skew — that feature stores exist to solve.
Fundamentals
The serving layer as the last stage of the lifecycle
It’s useful to think of the data engineering lifecycle as ending in a fan-out rather than a single destination. Once data is extracted, stored, and transformed into clean, modeled tables, it branches out to wherever it creates value:
- humans explore visuallyBI toolTableau, Looker, Power BI
- operational SaaS toolsReverse ETL
- ML training & inferenceFeature store
- analysts, data scientistsAd-hoc SQL / notebooks
Each of these consumption patterns has different requirements for freshness, latency, and query shape, and each fails differently when the data engineer treats “the model is built” as the finish line. A mart that’s perfect for a nightly BI refresh might be far too slow, or far too stale, for a feature store powering real-time inference. Designing the serving layer well means understanding, for each consumer, not just what data they need but how fast, how often, and in what shape they need to get it — and that understanding has to happen before the pipeline is built, not after a dashboard turns out to be unusably slow.
Business Intelligence (BI) tools
BI tools exist to let people who don’t write SQL — or who don’t want to write it every time — explore data visually and build dashboards without engineering help. The three dominant players work from the same underlying idea (connect to a warehouse, model or map its tables, let users drag fields onto a canvas to build charts) but differ in philosophy:
| Tool | Model | Notable strength |
|---|---|---|
| Tableau | Connects to a warehouse or extracts data into its own in-memory engine (Hyper) | Best-in-class visual exploration and ad-hoc “drag and drop” analysis; strong for analysts who want to interrogate data freely |
| Looker | Query-pushdown — always queries the live warehouse, never extracts data | LookML, a version-controlled semantic modeling layer (below) that centralizes metric and dimension definitions |
| Power BI | Connects to a warehouse or imports data into its own columnar engine (VertiPaq) | Deep integration with the Microsoft ecosystem (Excel, Azure, Teams); DAX for calculated measures |
What all three have in common is that they are only as good as the data underneath them. A BI tool is a lens, not a fix — it renders whatever the warehouse gives it, at whatever speed the warehouse can answer a query. This is why the earlier note on data modeling matters so directly here: a well-modeled warehouse — specifically, a star schema (./06-data-modeling-and-warehousing.md) with clean fact and dimension tables — is what makes BI tools feel fast and trustworthy, while a warehouse full of ungoverned raw tables and ad-hoc joins is what makes them feel slow and confusing.
Two concrete mechanisms explain why the model matters this much:
- Query speed. A BI tool typically generates SQL on the fly based on what a user drags onto a canvas. If the underlying tables are already a denormalized star schema — one join from fact to each dimension — the generated SQL is a handful of simple joins and aggregations, and a modern columnar warehouse answers it in seconds. If instead the BI tool has to join eight normalized tables and disambiguate which of three different
created_atcolumns is the “right” one, every dashboard load turns into an expensive, fragile query, and users experience that as “BI is slow.” - Trustworthiness. A star schema with clearly named, well-documented facts and dimensions (
fct_orders,dim_customer) gives users a small, well-understood vocabulary to build from. A pile of raw, undocumented tables invites every analyst to define “an order” or “an active customer” slightly differently, and that’s the seed of the exact problem the semantic layer (next) exists to prevent.
Self-service exploration and its limits
The promise of BI tools is self-service: a marketing manager builds their own chart of campaign performance without filing a ticket with the data team. This genuinely works when the warehouse is well modeled, because self-service exploration is really just “let non-engineers combine pre-defined, trustworthy building blocks (facts, dimensions, metrics) themselves,” rather than “let anyone write arbitrary SQL against raw data.” The data engineer’s job is to build and maintain those building blocks; the BI tool and the semantic layer are what make combining them safe for people without a data engineering background.
Key Concepts
The semantic layer: one metric, defined once
The single most important idea in this note is the semantic layer: a governed layer, sitting between the warehouse and every BI tool or dashboard, where business metrics and dimensions are defined exactly once — “active user,” “revenue,” “churned customer” — and every downstream consumer references that single definition instead of recalculating it themselves.
The problem the semantic layer solves is a familiar one to anyone who has worked at a company with more than one dashboard: five different dashboards show five different numbers for “revenue,” because five different people wrote five different SQL queries, each with a slightly different assumption. One analyst’s “revenue” includes tax; another’s excludes refunds; a third joins on order_date while a fourth joins on shipped_date. None of them are lying — they just each made an independent, reasonable-sounding choice, and there was no single place to look up the “official” definition. The result is a credibility crisis: leadership stops trusting dashboards, and every number gets re-litigated in meetings instead of driving decisions.
A semantic layer fixes this structurally rather than through discipline or memos. The metric is defined once, typically as code:
# Example: a metric defined once in a semantic layer (dbt Semantic Layer / MetricFlow style)
metrics:
- name: revenue
label: "Revenue"
description: "Net revenue recognized: gross order amount minus refunds, excluding tax."
type: simple
type_params:
measure: net_order_amount
filter: |
{{ Dimension('order__status') }} = 'completed'
# Example: the same idea expressed in LookML, Looker's semantic modeling layer
measure: revenue {
type: sum
sql: ${TABLE}.net_order_amount ;;
filters: [order.status: "completed"]
description: "Net revenue recognized: gross order amount minus refunds, excluding tax."
}
Every dashboard, every ad-hoc query routed through the semantic layer, and every BI tool connected to it now inherits the same revenue — change the definition once (say, finance decides revenue should now be recognized on shipped_date instead of order_date) and every consumer updates consistently, instead of someone having to hunt down and edit twelve separate SQL queries scattered across dashboards nobody remembers building.
LookML deserves specific mention here because it was one of the earliest widely-adopted implementations of this idea: instead of each Looker dashboard containing its own SQL, LookML defines dimensions and measures once, in version-controlled .lkml files, and every Looker “Explore” and dashboard is generated from that shared model. The more recent dbt Semantic Layer (built on MetricFlow) generalizes the same idea outside of any single BI tool — metrics are defined once in the dbt project alongside the models they’re built from, and any BI tool with a semantic layer integration (not just one vendor’s) can query them consistently. The specific tool matters less than the principle: metric definitions belong in a governed, version-controlled layer, not scattered across dashboard-specific SQL.
Streamlit and lightweight internal apps
Not every internal data need justifies the cost — in licensing, setup, and governance overhead — of a full BI platform. Streamlit (and similar frameworks like Gradio or Dash) lets a data engineer or analyst turn a Python script into an interactive web app in a few dozen lines, without touching HTML, CSS, or JavaScript:
import streamlit as st
import pandas as pd
df = pd.read_sql("select * from fct_orders", con=warehouse_connection)
st.title("Orders Dashboard")
region = st.selectbox("Region", df["region"].unique())
filtered = df[df["region"] == region]
st.metric("Total Revenue", f"${filtered['amount_usd'].sum():,.2f}")
st.bar_chart(filtered.groupby("order_month")["amount_usd"].sum())
Its niche relative to Tableau, Looker, or Power BI is narrow but real: quick internal tools for a small audience — a one-off exploration app for a data science team, an internal QA tool for reviewing pipeline output, a prototype dashboard before deciding whether a metric deserves a permanent home in the real BI stack. What it deliberately doesn’t provide is what a full BI platform is built for: enterprise governance, row-level security, a shared semantic layer, scheduled distribution to hundreds of business users, or a polished self-service experience for non-technical staff. The rule of thumb: reach for Streamlit when the audience is small, technical, and internal, and reach for the BI platform when the audience is broad, non-technical, and needs metrics that agree with every other dashboard in the company.
Data analytics practice: what analysts do with what engineers build
It’s worth being precise about the division of labor implied by everything above. Descriptive analytics — what happened — is largely a BI-tool exercise: it answers questions like “what was revenue last quarter” or “which region grew fastest,” and it’s exactly the kind of question a well-modeled warehouse and a semantic layer are built to answer quickly and consistently. Beyond that sits deeper analysis: diagnostic analytics (why did it happen), and work that blends into data science — cohort analysis, statistical testing, causal inference, forecasting.
The data engineer’s role in this deeper work is enabling, not performing. A data engineer does not typically run the regression or design the cohort analysis; a data engineer makes sure the pipeline that feeds that analysis produces clean, well-documented, performant data models, so the analyst or data scientist can spend their time on the actual analytical question instead of on data archaeology — chasing down why a join produces duplicate rows, or why a column’s meaning changed silently three months ago. The clearest sign of a healthy handoff is that analysts spend the overwhelming majority of their time asking and answering business questions, and only rarely need to go digging in raw source tables because something in the modeled layer doesn’t add up.
A/B testing infrastructure
Experimentation is one of the sharpest examples of “the data engineering matters even though the interesting question is a statistics question.” An A/B test is only as trustworthy as the events feeding it, and there are several specifically engineering failure modes that can silently invalidate an otherwise well-designed experiment:
- Reliable assignment logging. When a user is bucketed into a variant, that assignment event has to be logged reliably and exactly once — logged too late, dropped under load, or logged multiple times, and the experiment’s denominator (how many users were actually exposed to each variant) becomes wrong before any outcome data is even considered.
- Reliable outcome logging and correct joins. The outcome event (a purchase, a click, a signup) has to be joined back to the correct assignment — by a stable user or session identifier, within the correct time window relative to exposure. A pipeline bug that joins outcomes to the wrong assignment (e.g., using a session ID that resets across devices, or a time window that leaks pre-experiment behavior into the analysis) can make a neutral feature look like a winner or a winning feature look neutral.
- Sample Ratio Mismatch (SRM). If an experiment is configured as a 50/50 split but the logged assignment data shows, say, a 52/48 split that’s statistically significant, that’s a sample ratio mismatch — a strong signal that something in the pipeline is broken (a bug that causes one variant’s assignment events to be dropped more often, a redirect or caching layer that behaves differently per variant, a bot-filtering rule that disproportionately removes one group) rather than a real randomization problem. SRM checks should run automatically before anyone looks at the actual metric results, because an experiment with SRM is not measuring what it claims to measure, no matter how compelling the topline numbers look.
The data engineering ask here is narrow but non-negotiable: an event pipeline (./08-etl-elt-and-data-pipelines.md, ./11-streaming-and-realtime-data.md) that captures assignment and outcome events with strong delivery guarantees, stable identifiers that survive the length of the experiment, and monitoring that catches SRM and other pipeline anomalies automatically — because a downstream statistician cannot correct for missing or mis-joined data no matter how sophisticated the significance test.
Feature stores and training-serving skew
Machine learning consumption of the serving layer has a problem BI dashboards don’t: the same feature — say, “number of purchases by this customer in the last 30 days” — has to be computed identically in two very different contexts. During training, it’s computed in bulk over historical data, often with a batch SQL or Spark job. During inference (serving live predictions), it has to be computed for a single entity, right now, often with millisecond latency budgets, sometimes from a completely different code path or even a different team’s service. If those two computations drift apart even slightly — a different time-zone handling, an off-by-one in the lookback window, a slightly different definition of “purchase” — the model sees different feature values in production than it was trained on, and its accuracy silently degrades. This is training-serving skew, and it’s one of the most common causes of a model that scored well offline but performs poorly in production.
A feature store (Feast, Tecton, or a cloud-native offering like SageMaker Feature Store) is the specialized serving layer built to close this gap. It computes a feature once, from a defined transformation, and stores it in two forms kept consistent with each other: an offline store (typically the warehouse or a lake, holding historical feature values for training) and an online store (a low-latency key-value store, typically Redis-backed, holding the current value of each feature for real-time inference lookups). Because both stores are populated from the same feature definition, a model trained against the offline store’s historical values sees the same feature semantics at inference time from the online store — eliminating skew by construction rather than by convention.
The data engineer’s role in the ML pipeline mirrors their role in analytics: reliable feature computation, not model training. Building and operating the pipelines that compute features correctly, backfill historical values for training, and keep the online store fresh (often on a streaming basis for features that need to reflect very recent activity) is squarely data engineering work; choosing the model architecture and tuning it is squarely a machine learning role. Where deeper ML/MLOps material — model training, evaluation, deployment — exists elsewhere in this knowledge base, this note stays scoped to the serving-layer half of that boundary.
Consumption patterns and their failure modes
Different consumers of the serving layer need different things from the data engineer, and neglecting the serving layer breaks each of them in a characteristic way:
| Consumption pattern | What the data engineer must provide | Common failure mode if neglected |
|---|---|---|
| BI dashboard | A well-modeled star schema, fast query performance, metrics defined in a shared semantic layer | Dashboards are slow, or five dashboards disagree on the same metric because each was built with ad-hoc SQL |
| Reverse ETL | A trustworthy, deduplicated mart table with a stable key matching the destination system’s records | Wrong or stale data gets pushed into a CRM/marketing tool, silently corrupting the operational system business teams rely on |
| ML feature store | Features computed identically for training (offline) and inference (online), refreshed on a known cadence/latency | Training-serving skew — the model degrades in production despite scoring well offline, and the cause is invisible without comparing offline vs. online feature values directly |
| Ad-hoc analyst query | Clean, documented, performant tables with clear naming and lineage | Analysts waste time reverse-engineering raw tables, or draw wrong conclusions from undocumented columns/joins |
Best Practices
- Treat the semantic layer as mandatory infrastructure, not a nice-to-have. The moment more than one dashboard or tool needs the same metric, define it once in a governed layer (LookML, dbt Semantic Layer, or equivalent) rather than letting it be recalculated ad hoc — the “five dashboards, five numbers” problem is far cheaper to prevent than to debug after leadership has already lost trust in the data.
- Model for the consumer, not just for storage. A star schema optimized for BI query speed may not be the right shape for a feature store’s low-latency lookups; design the serving shape around how data will actually be queried, not only around how it’s cheapest to store.
- Never let a BI tool paper over a bad model. If a dashboard is slow or confusing, the fix is almost always upstream — better modeling, better joins, better documentation — not a more complex BI-tool workaround layered on top of a messy warehouse.
- Instrument A/B testing pipelines with automatic SRM and event-delivery checks, and treat those checks as a gate that runs before anyone looks at experiment results, since a broken pipeline can produce a confident, wrong answer that looks statistically valid.
- Build feature computation once and share it between training and serving, via a feature store or an equivalent shared transformation, rather than letting a training pipeline and an inference service independently reimplement the same feature logic — that duplication is exactly where training-serving skew creeps in.
- Reach for lightweight tools (Streamlit) for small, internal, technical audiences, and for full BI platforms when the audience is broad or non-technical — matching the tool to the audience avoids both over-engineering a one-off internal script and under-serving a company-wide reporting need.
- Remember that “done” means “consumed,” not “modeled.” A pipeline that lands perfect data in a mart nobody queries, a feature nobody’s model uses, or a metric nobody’s dashboard references hasn’t delivered value yet — closing the loop with the actual consumer, as covered further in
./20-career-and-best-practices.md, is part of the job, not a bonus.