題目與適用情境
公司有 30,000 個資料集,每天透過 Airflow、Spark、dbt 和 Kafka 執行 200,000 次資料工作。團隊需要回答三個 問題:某個資料集或欄位從哪裡來;準備修改它時會影響什麼;某次具體執行究竟讀取和產出了什麼。請設計一套 資料血緣系統,擷取表級與欄位級相依關係,支援上下游遍歷和歷史時間點查詢,正確處理重試、失敗與部分寫入, 遵守中繼資料存取權限,並告訴使用者目前血緣圖是否完整到足以支撐決策。
本題假設事件寫入尖峰為每秒 500 筆,三跳查詢通常應在 2 秒內完成,詳細執行歷史保留 1 年。這些數字是面試 情境輸入,不是產業基準;方案也要說明上線後如何以真實流量重新校準容量和目標。
2026 年公開的資料工程面試材料直接詢問如何設計資料血緣,並涵蓋表、欄位、工作三個層級,以及中繼資料擷取、 統一命名和變更影響。OpenLineage 1.50.0 可作為事實基礎:核心實體是 Job、Run、Dataset;Job 與 Dataset 由 命名空間和名稱識別;Run 使用 UUID;執行事件有明確狀態;facet 可擴充 schema 和欄位相依等中繼資料。核心 考察能力是中繼資料建模、資料工作語意、影響分析和資料治理,因此 category 為 data。
面試官評估重點
第一,候選人是否先處理識別和語意。同一張表若同時以 orders、prod.orders 和倉儲 URL 出現,或把一次 重試誤認為新工作,圖資料庫再快也沒有意義。必須先定義資源主鍵、環境邊界、工作定義、執行 ID、欄位路徑和 版本語意。
第二,血緣是否貼近真實執行。程式碼或編譯產物提供的宣告血緣適合發布前分析;某次 Run 上報的觀測血緣能證明 該次執行實際讀寫了什麼。失敗執行可能已留下暫存或部分輸出,而執行成功也不等於資料正確。好的設計會區分 事件證據、Run 狀態、宣告邊、觀測邊和輸出發布狀態。
第三,系統設計是否閉環。答案應涵蓋產生端整合、持久且冪等的寫入路徑、不可變原始事件、標準化、時態圖物化、 遍歷索引、歷史重放、覆蓋率、新鮮度和權限控制。只說「用圖資料庫」略過了最困難的部分。
最後,能否誠實表達可信度。若關鍵工作只有 60% 接入,介面卻畫出一張完整的圖,影響分析會誤導發布決策。 每條邊都應顯示來源、觀測時間、推導方式和缺口,讓使用者知道證據可以支援什麼結論。
回答前需要釐清的問題
- 血緣要支援哪些決策? 事故排查、發布前影響分析、資料探索、PII 傳播追蹤和稽核,對新鮮度、歷史與
精度的要求不同。
- 什麼算一個 Dataset? 表、檢視、檔案、物件前綴、Kafka Topic、物化檢視、儀表板和特徵都要定義粒度。
把每個檔案都建成永久節點,可能讓圖失去可用性。
- 需要宣告血緣、觀測血緣,還是兩者都要? 前者能描述尚未執行的改動,後者能關聯一次具體 Run;介面
不能默默混成同一種事實。
- 欄位級精度要到哪裡? 欄位直接參與值計算,與只參與 JOIN、過濾、分組、排序、視窗或條件判斷,影響
語意不同。有些引擎能提供邏輯計畫,有些只能可靠提供表級血緣。
- 跨環境如何識別 Dataset 和 Job? 需要定義 namespace、規範名稱、別名、大小寫、重新命名和負責人;
顯示名稱不能當穩定主鍵。
- 失敗或部分完成後怎麼處理? 部分輸出是已發布、隔離還是回復?影響查詢是否既要保留嘗試記錄,又不能
把它當成目前可信事實?
- 歷史查詢要保留多久? 保留 1 年 Run 事件,不代表所有展開後的欄位級邊都必須在低延遲層保存 1 年。
- 哪些中繼資料敏感? SQL、欄位名稱、負責人、PII 標籤,甚至資料集是否存在都可能受保護,遍歷必須遵守
目錄權限。
- 容量和服務目標是什麼? 需要確認事件率、圖規模、遍歷深度、延遲百分位、復原目標,以及執行結束到血緣
可見的最大延遲。
30 秒回答架構
「我會先定義規範的 Job、Run、Dataset 和 Field 識別,再區分宣告與觀測、表級與欄位級血緣。產生端透過驗證 身分、冪等且有持久日誌的閘道上報帶版本事件;消費者保留原始證據並建立上下游時態索引。失敗輸出先作為診斷 證據,確認發布後才進入目前血緣。遍歷受深度、時間點和權限限制,並顯示來源與缺口。我會監控延遲、預期工作 覆蓋率、終態完整率、未解析識別、陳舊邊和抽樣路徑正確率,先涵蓋關鍵工作的表級血緣,再增加可靠的欄位級擷取。」
分步深入解答
第一步:先定義真相模型,再選儲存。
至少需要四類記錄:
| 記錄 | 穩定識別 | 用途 | |---|---|---| | Dataset | (namespace, name) 加環境 | 表、Topic、檢視或刻意選擇的邏輯資料集 | | Field | Dataset 識別加規範欄位路徑 | 某個 schema 版本內的欄位或巢狀欄位 | | Job | (namespace, name) 加定義版本 | 週期性轉換、工作、查詢或模型 | | Run | 用戶端產生的 UUID | Job 的一次執行;只有真正獨立執行才把重試建成新 Run |
Dataset 的 namespace 通常來自資料來源,Job 的 namespace 來自排程器或處理系統。別名分開保存並帶有效期間。 把 analytics.orders 改名為 analytics.sales_orders 時,應產生明確的重新命名或別名事件,不能靠字串相似度 自動合併或拆分。
編譯 SQL、dbt manifest 或設定得到的宣告血緣,與 Run 實際送出的觀測血緣分開建模;表級邊與欄位級 邊也分開。欄位邊記錄輸出欄位、輸入欄位、轉換類型,以及直接值推導或間接影響。OpenLineage 的欄位級模型把 直接相依分為原值、轉換、彙總,把間接影響分為 JOIN、GROUP BY、FILTER、SORT、WINDOW 和 CONDITIONAL。 欄位改值、改型別或刪除時,這些差異會改變影響判斷。
每條邊至少帶 validFrom、可選的 validTo、observedAt、producer、來源事件、Job 版本、Run ID、Run 狀態、 血緣種類,以及信心或推導方式。這樣一條箭頭才回答得了「何時有效」和「證據來自哪裡」。
第二步:盡量在執行位置擷取。
優先使用引擎或社群維護的整合:排程器監聽工作生命週期;Spark 讀取邏輯計畫;dbt 使用 manifest 與 run results; 倉儲和串流處理系統讀取連接器、查詢歷史或執行計畫。不要把正則解析 SQL 當主要方法。動態 SQL、巨集、預存 程序、暫存物件、UDF 和執行時分支都會讓純文字推斷漏報或誤報。
在血緣內容外定義一層帶版本的平台寫入信封:
{
"eventId": "producer-unique-id",
"producer": "spark-prod-eu",
"schemaVersion": "1.0",
"emittedAt": "2026-07-19T00:00:00Z",
"job": { "namespace": "spark-prod", "name": "daily_orders" },
"runId": "53ee3770-86fa-4cb9-8c31-a09072dd88f7",
"state": "COMPLETE",
"inputs": [{ "namespace": "warehouse-prod", "name": "raw.orders" }],
"outputs": [{ "namespace": "warehouse-prod", "name": "mart.daily_orders" }]
}這裡的 eventId 是本平台為冪等規定的信封欄位,不能說成所有外部血緣標準都強制包含它。產生端重試時重用 同一 ID。閘道驗證產生端身分、schema 相容性和大小,補上接收時間,並在回覆成功前寫入分區持久日誌。無效 事件進入含原因、產生端和安全內容參照的隔離串流,不能只留一行日誌。
依 Run ID 分區能保持單次執行內的局部順序,也讓無關 Run 並行消費。事件可能延遲或時鐘偏移,因此同時保存 發出與接收時間,並依狀態機處理。OpenLineage 定義 START、RUNNING、COMPLETE、ABORT、FAIL、 OTHER 六種狀態;終態不能被延遲的 START 重新打開,但原始事件仍完整保留。
第三步:標準化時保留來源。
標準化消費者把各整合內容轉成規範識別和邊語意,解析已登記別名、大小寫、環境、暫存 Dataset 和巢狀欄位 路徑。未知識別進入待解析佇列,禁止猜測。原始事件、標準化結果、解析器版本和警告維持關聯,錯誤映射才能修正 並重放。
schema 變更會產生帶版本的欄位定義。刪除後再建立同名 customer_id,不能預設它具有連續歷史。schema hash 或目錄版本配合有效期間,讓歷史查詢選到正確欄位。串流工作以 Topic 和轉換 Job 為穩定節點;分區與 offset 較適合保存在 Run 證據中,避免把每個分區-offset 對永久展開成圖節點。
執行狀態和發布狀態要分開。COMPLETE 代表執行結束,不代表業務資料正確。FAIL 或 ABORT 也可能上報輸入 和嘗試輸出;這些邊可用於排障,但只有符合原子提交標記或獨立品質閘門後,才進入目前已發布血緣,且必須明確 標示。
第四步:從不可變事件建立時態圖投影。
持久日誌和不可變物件儲存是復原真源,消費者產生三種投影:
- 中繼資料儲存:規範 Job、Dataset、Field、schema、別名、負責人和權限策略。
- 時態邊儲存:宣告與觀測血緣、有效期間和執行來源。
- Run 儲存:生命週期事件、輸入輸出快照、狀態和診斷資訊。
先做一筆可重算的容量估算,避免把每日 Run 數誤認為事件吞吐。每天 200,000 次 Run 各自至少發一個 START 和一個 終態,每天就是 400,000 筆事件,平均約每秒 4.6 筆。若典型 Run 發一個 START、兩個 RUNNING 和一個終態, 則每天 800,000 筆,平均約每秒 9.3 筆;題設每秒 500 筆是突發目標,不是平均值。再假設原始內容平均 20 KB, 800,000 筆約占每天 16 GB、每年 5.8 TB,尚未計算壓縮和副本。上線後必須實測單筆大小與每 Run 事件數,因為 欄位級 facet 會大幅改變估算。
低延遲查詢同時維護上游和下游鄰接索引,鍵包含規範節點 ID 與有效版本或時間桶。廣度優先遍歷必須限制深度、 節點數、邊類型、環境和歷史時間點;碰到上限要回傳「結果不完整」和續查資訊。圖資料庫可以實作,但不是必選。 目前規模也可能以具索引的關聯式邊表或鍵值鄰接服務更簡單,應以真實扇出和時態條件基準測試決定。
欄位級邊遠多於表級邊。表級邊和常用、近期欄位鄰接放在熱層,低頻舊欄位邊放到壓縮歷史層。不要預先計算 全圖遞移閉包:高扇出、版本變動和權限會使更新成本失控。快取鍵應包含節點、方向、深度、時間點、邊類型和 權限範圍,相關邊版本改變後失效。
第五步:明確定義查詢契約。
介面至少要支援:Dataset 或 Field 的上下游限深遍歷;區分直接與間接相依的 schema 變更影響分析;顯示某次 Run 的精確輸入、輸出、Job 版本、生命週期和發布狀態;為每條邊回傳宣告或觀測、來源和最後觀測時間;對未 接入 Job、未解析識別、陳舊 producer 與截斷遍歷回傳缺口標記。
權限不能等遍歷完成才過濾。隱藏 Dataset 的名稱和存在本身也可能敏感。展開鄰居時就要執行策略,依治理規則 省略或替換受保護節點,避免度數洩漏隱藏鄰居,並稽核敏感查詢。快取鍵包含授權範圍,避免把一個人的圖回傳給 另一個人。
針對三跳 2 秒目標,按方向、深度、扇出、歷史條件、表級或欄位級分別量測 P50、P95、P99。超過節點預算時 回傳續查 token 或明確截斷,不能默默給出殘缺圖。
第六步:支援重放、回填和復原。
消費者記錄持久日誌 offset。因處理採至少一次,投影寫入以 eventId 和投影版本保證冪等。標準化邏輯出錯時, 發布新版解析器,從原始事件重建影子投影,比對數量並抽樣驗證路徑,通過後再切換讀取,不能直接覆蓋唯一線上圖。
原始事件依題設加密保留 1 年並執行生命週期政策。快照可以縮短復原時間,但必須證明「快照加後續事件」能得到 相同投影。定義復原目標,演練寫入區域故障,並確認產生端重試不會增加重複邊。
歷史血緣要依邊的有效期間和觀測時間判斷,不能在目前圖上隨便貼一個過去時間。查詢上個月時,要解析當時有效的 識別、schema 版本和授權邊;產生端從未提供歷史時,應明確回傳限制。
第七步:把可信度做成產品能力。
至少監控以下訊號:
| 訊號 | 能發現什麼 | |---|---| | 各 producer 寫入延遲和拒收率 | 圖是否新鮮,協定是否仍相容 | | 預期 Job 上報覆蓋率 | 哪些已排程 Job 沒有血緣事件 | | 終態事件完整率 | 只有 START、沒有終態的 Run | | 識別解析失敗率 | 因未知或衝突名稱而懸空的邊 | | 觀測邊新鮮度 | 多久沒有由近期成功發布再次證實 | | 依關鍵等級區分的表級與欄位級覆蓋 | 重要資產是否達到需要的精度 | | 抽樣路徑正確率 | 已知輸入輸出和真實執行能否回傳預期路徑 | | 遍歷截斷率與延遲 | 高扇出是否讓服務目標失真 |
覆蓋率必須有獨立分母。以排程器清單或倉儲查詢歷史中的 Run 對比實際血緣事件,不能只統計收到多少筆。介面 顯示「12 分鐘前觀測」「僅宣告」「沒有欄位級血緣」「17 個上游 Job 中 2 個未接入」等具體標籤,不用一個不透明 總分掩蓋問題。
準備包含原值、彙總、JOIN、過濾、重新命名、重試、失敗和部分發布的確定性案例。生產環境抽樣時,從引擎計畫、 發出事件、標準化邊到查詢結果端到端核對;每次投影發布都核對節點數和邊數。
第八步:依決策價值漸進上線。
先涵蓋關鍵業務領域和表級血緣,登記規範識別與負責人,接入影響最大的排程器和引擎,並先公開新鮮度與覆蓋 缺口。之後再為能可靠輸出邏輯計畫的引擎增加欄位級血緣、發布前宣告血緣、歷史查詢和 PII 傳播。
成功指標應對應真實決策:關鍵改動中具有可用發布前影響報告的比例;事故中能定位第一個異常上游邊界的比例; 未解析識別下降幅度;關鍵 producer 覆蓋率。節點很多、圖看起來很密都不是成功指標。
高品質示範回答
「我先定義決策和識別。Dataset 與 Job 在環境內以規範 namespace 和 name 識別,Field 再加規範路徑與 schema 版本,Run 是一次由 UUID 識別的執行。別名和重新命名是帶時間範圍的明確映射。編譯計畫得到的宣告血緣和執行 產生的觀測血緣分開,表相依與欄位相依也分開。
擷取端使用 Airflow、Spark、dbt 和 Kafka 相關處理框架的維護型整合。Spark 與 SQL 引擎盡量讀取邏輯計畫, 因為正則無法可靠處理動態 SQL、巨集、JOIN 和執行時分支。平台信封包含穩定 event ID、producer、協定版本、 Job、Run ID、生命週期和輸入輸出。閘道驗證身分和格式後,先追加到持久日誌再確認;相同 event ID 重送冪等, 無效事件進入可見隔離串流。
原始事件不可變。標準化層解析已登記別名,產出帶版本的 Job、Dataset、Field 和邊,同時保留來源事件與解析器 版本。未知識別絕不猜測。START、RUNNING 能補充證據,COMPLETE、ABORT、FAIL 是終態。失敗 Run 仍可用於排障,但嘗試輸出不會自動成為目前已發布血緣;COMPLETE 也只代表執行結束,資料品質另行判斷。
消費者建立規範中繼資料、時態邊和 Run 三類儲存,並維護上下游鄰接索引。每條邊帶有效期間、觀測時間、 producer、Job 版本、Run、狀態、宣告或觀測種類,以及欄位直接或間接轉換。歷史查詢選擇當時有效的識別和邊。 我不會預先計算全圖遞移閉包,因為高扇出、版本變動和權限會讓它難以更新。
對 30,000 個資料集、每日 200,000 次 Run 和每秒 500 筆尖峰事件,我會先採用分區持久日誌和具索引的關聯式 或鍵值投影,再以真實扇出評測專用圖儲存。欄位級邊是主要膨脹項,因此常用近期鄰接放熱,舊明細壓縮。三跳 2 秒要按 P50、P95、P99 和扇出分別驗證;每次查詢有深度和節點預算,超限必須明確截斷或續查。
查詢在展開圖時執行權限,不能洩漏隱藏節點的名稱、存在或鄰居數量,快取也要包含權限範圍。結果顯示來源與 缺口:僅宣告、最近觀測時間、未解析識別、陳舊 producer、缺少欄位級血緣或未接入 Job。
投影必須可重建。原始事件保留 1 年,消費者依 offset 推進並冪等寫入。解析器或 schema 出錯時,建立影子投影, 核對並驗證已知路徑後再切換。快照只用來縮短復原,仍要證明加上後續事件可確定性還原。
最後,我以預期 Job 覆蓋率、終態完整率、識別解析失敗、邊新鮮度、關鍵表欄覆蓋、拒收事件和抽樣路徑正確率 衡量可信度,分母來自排程器清單與查詢歷史。先上線財務、客戶等關鍵領域的表級血緣並公開缺口,再在擷取可靠 時補欄位級和宣告血緣。系統價值在於更安全地改動和用證據定位事故,不在於圖裡有多少節點。」
常見錯誤與改進建議
- 先選圖資料庫 → 儲存解決不了識別、執行狀態、歷史和缺少接入 → **先定義實體、證據、生命週期和查詢
契約。**
- 把顯示名稱當主鍵 → 別名、大小寫、環境和重新命名會錯誤拆分或合併節點 → **使用規範 namespace/name,
並明確維護帶有效期間的別名。**
- 把宣告和觀測血緣混在一起 → 編譯可能路徑與實際執行路徑可能不同 → 每條邊保存種類與來源並允許篩選。
- 把失敗 Run 的嘗試輸出都升級為目前邊 → 部分檔案或表會變成虛假事實 → **保留排障證據,但符合發布
語意後才啟用。**
- 認為 COMPLETE 等於資料正確 → 執行可以成功產出重複或無效資料 → Run 生命週期和資料品質分開建模。
- 所有 SQL 都用正則解析 → 動態 SQL、方言、巨集和巢狀運算式會產生錯誤相依 → **優先使用執行計畫和
可靠解析器,並公開未支援範圍。**
- 沒有事件協定就依內容 hash 去重 → 兩個進度事件可能內容相同,重送時間又可能不同 → **在平台信封要求
producer 穩定 event ID。**
- 只保留目前圖 → 歷史影響和事故還原無法完成 → 保留不可變事件和時態邊版本。
- 預先計算全部遞移路徑 → 扇出、版本和權限造成過高失效成本 → 使用受限遍歷和定向快取。
- 到最終回應才做權限過濾 → 展開圖和快取階段已可能洩漏隱藏節點 → 展開鄰居時執行策略並隔離快取。
- 以收到的事件數當覆蓋率 → 靜默 producer 在事件與指標中同時消失 → 和排程器清單或查詢歷史對比。
- 缺口未知卻畫成完整圖 → 使用者會做出不安全的變更判斷 → **回傳新鮮度、來源、未解析識別和缺少
producer。**
延伸追問
追問一:Spark Run 失敗前已寫出一個表分割區,這條邊要顯示嗎?
保留 Run 和嘗試輸入輸出邊作為觀測排障證據,標記為 FAIL 且未發布。是否進入目前生產圖,取決於儲存提交與 發布策略:分割區已可見時,先顯示為失敗或可疑版本,直到回復或驗證;原子寫入已中止時,不啟用這條邊。同時 保存執行證據和發布狀態,既不遺失鑑識資訊,也不把部分輸出包裝成可信事實。
追問二:如何發現一個 producer 悄悄停止上報?
只看已收事件無法發現完全缺席的 producer。應從 Airflow 排程、Spark 歷史、dbt run results、倉儲查詢日誌等 獨立控制面建立預期 Run 清單,在延遲窗口內依規範 Job 和 Run 識別關聯血緣事件。分別警示缺少開始、缺少終態 和關鍵等級覆蓋率下降,並區分工作已下線與整合故障。
追問三:工作尚未執行,怎麼做發布前影響分析?
從待發布的編譯計畫或 manifest 擷取宣告血緣,與目前定義比較差異,再從被刪除或修改的輸出往下游遍歷。結果 明確標示為發布前宣告證據,並顯示歷史觀測血緣是否支持或衝突。關鍵資產可在 CI 中要求負責人審查,但不能把 未來的動態分支說成已經觀測到的事實。
追問四:為什麼不把所有內容放進一個圖資料庫?
單一資料庫在基準測試通過後可以採用,但 Run 事件歷史、不可變重放、中繼資料搜尋和低延遲鄰接的存取模式不同。 把持久事件真源與可重建投影分開,有利於復原和獨立演進。先用滿足需求的最少儲存,量測扇出和時態查詢成本, 再決定專用儲存是否值得它的維運成本。
追問五:欄位級血緣讓邊數增加幾百倍,先降級什麼?
優先保護正確性和關鍵決策。保留表級血緣和高關鍵性業務領域的近期欄位級血緣,把舊明細移到壓縮歷史層,低頻 欄位路徑非同步計算。遍歷設置預算並明確回傳部分結果,不能悄悄以表級猜測冒充欄位級答案。持續依引擎和業務 領域統計欄位覆蓋率,確保降級可量測且可復原。