在資料驅動的決策過程中,確保資料的可靠性和可追溯性至關重要。資料血統分析提供資料在生命週期中的完整軌跡,追蹤資料從來源到最終應用的完整流程。傳統的表級別血統分析缺乏精細度,難以滿足快速問題排查的需求。欄位級別血統分析則能追蹤到更細粒度的資料變化,對於快速定位問題根源至關重要。構建欄位級別血統分析系統需要克服諸多技術挑戰,例如處理複雜的 SQL 轉換邏輯和管理大量的元資料。

在實際應用中,欄位級別血統分析能有效縮短問題檢測和解決時間,例如快速定位收入報告中的異常資料、識別和移除過時的資料欄位、管理敏感的個人資訊等。透過整合資料血統分析與現有的根因分析工作流程,能大幅提升資料團隊的效率,降低資料錯誤帶來的風險。

資料血統分析(Data Lineage)在現代資料系統中的重要性

在現代資料驅動的商業環境中,資料的可靠性和可追溯性至關重要。資料血統分析(Data Lineage)提供了一個對資料集在整個生命週期中的完整軌跡,從資料倉儲或資料湖中的攝取(ingestion),到最終在分析層中的視覺化。簡單來說,資料血統分析記錄了資料如何從一個點到達另一個點。在資料管道(data pipelines)的背景下,血統分析追蹤了上游來源系統(如資料倉儲和資料湖)與下游依賴項(如分析報告和儀錶板)之間的關係,從而提供了一個完整的資料演變檢視。

資料血統分析的基本需求

在過去的幾年中,各行業的資料團隊一直依賴於**表級血統分析(table-level lineage)**來改進他們的資料可靠性工作流程,透過生成上游和下游依賴關係的對映。雖然在宏觀層面上很有用,但表級血統分析並不能為團隊提供足夠的粒度來瞭解資料管道究竟為何或如何中斷。

關鍵功能需求

  1. 快速實作價值(Fast time to value)
    資料團隊希望能夠快速瞭解程式碼、操作和資料變更對下游欄位和報告的影響。因此,需要將資料物件之間的關係抽象到欄位級別(field-level);僅僅停留在表級別可能過於寬泛,無法滿足快速修復的需求。

  2. 安全架構(Secure architecture)
    通常,不希望資料血統分析直接存取使用者資料或個人可識別資訊(PII)。建議採用一種僅存取元資料(metadata)、日誌和查詢的方案,同時將資料保留在客戶的環境中。

  3. 自動化(Automation)
    欄位級別的血統分析產品往往採用手動方式,這增加了客戶的負擔。我們提倡投資於自動化方案,能夠根據資料生命週期中的變化自動更新資料資產。

  4. 與流行資料工具的整合(Integration with popular data tools)
    我們需要一個知識圖譜(knowledge graph),能夠自動生成整個資料管道中的節點,從資料倉儲或資料湖中的攝取一直到商業智慧或分析層。許多資料團隊需要與資料倉儲和資料湖技術(如Snowflake、Redshift、Databricks和Apache Spark)、轉換工具(如dbt、Apache Airflow和Prefect)以及商業智慧工具(如Looker、Tableau和Mode)進行整合。這要求解決方案能夠處理資料系統中每張表之間的所有可能的連線和關聯。

  5. 提取欄位級別的資訊(Extraction of column-level information)
    許多表級別的血統分析解決方案主要透過解析查詢日誌來獲得,這些方法無法提取解析後的欄位資訊——而這正是使用者理解資料中異常和其他問題所需的元資料。對於欄位級別的血統分析,我們建議深入到欄位級別(column-level),這是一個具有挑戰性的任務,後文將進一步討論。

資料血統分析的實際應用案例

根據基本的欄位級別血統分析,使用者還可以進一步聚合元資料以滿足不同的使用場景,例如操作分析(operational analytics)。例如,可以預先計算某個表及其每個欄位有多少下游表正在使用該欄位。這在識別資料品質問題對下游報告和儀錶板的影響時尤其有用。畢竟,誰不想進行無痛的根因分析(root cause analysis)

縮短問題檢測和解決時間

在最基本的層面上,欄位級別的血統分析可以用於大幅減少資料品質問題的檢測時間(time to detection)解決時間(time to resolution),目標是減少資料團隊進行根因分析所需的總時間。在分析能力方面,資料血統分析可以應用於多種場景,包括:

  1. 審查收入報告中的可疑資料
    一家擁有400名員工的金融科技公司使用儲存在Snowflake中的資料並透過Looker進行視覺化,生成每月收入預測。他們可以使用欄位級別的血統分析來追蹤倉儲中哪個表包含了報告中「可疑」數字的來源欄位,並透過這個過程發現問題的根源是一個未能成功執行的dbt模型。

  2. 減少資料債務(Reducing data debt)
    許多資料團隊利用資料可觀測性來廢棄頻繁使用資料集中的欄位,以確保不再使用過時的物件來生成報告。欄位級別的血統分析使他們能夠輕鬆識別某個欄位是否與下游報告相關聯。

  3. 管理個人可識別資訊(Managing PII)
    我們的幾位客戶處理敏感資料,需要了解哪些包含PII的欄位與下游儀錶板中的目標表相關聯。透過快速連線包含PII的欄位與使用者面對的儀錶板,客戶可以移除這些資訊或採取措施來廢棄或隱藏相關儀錶板。

這些使用案例僅僅觸及了領先的資料團隊如何利用欄位級別血統分析的表面。透過將其與現有的根因分析工作流程整合,可以為公司中的分析師和工程師節省時間和資源。

資料血統分析的設計

在實際構建欄位級別的資料血統分析時,首先需要設計一種方法來瞭解哪些欄位屬於哪些來源表,如圖7-2所示。這是一項具有挑戰性的任務,因為大多數資料轉換操作涉及多個資料來源。進一步複雜化的是,需要遞迴地解析原始來源和欄位,因為某些來源表可能是由其他子查詢衍生的別名(aliases of subqueries)。

-- 示例SQL查詢,用於展示欄位級別的血統分析
SELECT 
    orders.order_id,
    customers.customer_name,
    orders.order_date
FROM 
    orders
JOIN 
    customers ON orders.customer_id = customers.customer_id;

內容解密:

上述SQL查詢展示瞭如何透過orders表和customers表的連線操作,生成一個包含訂單ID、客戶名稱和訂單日期的新資料集。這種操作在欄位級別的血統分析中至關重要,因為它展示了不同資料來源之間的關聯。具體來說:

  • orders.order_idcustomers.customer_name分別來自orders表和customers表。
  • 連線條件orders.customer_id = customers.customer_id確保了資料的正確對齊。
  • 這種查詢的結果可以用於進一步的分析或報表生成,而欄位級別的血統分析可以幫助追蹤這些欄位的來源和變換過程。
  graph LR
    A[資料來源:Orders表] -->|包含訂單資訊|> B[資料來源:Customers表]
    B -->|包含客戶資訊|> C[連線操作]
    C -->|生成結果集|> D[欄位級別血統分析結果]
    D -->|包含order_id, customer_name, order_date|> E[最終報表]

圖表翻譯:
此圖示展示了資料從Orders表和Customers表流向最終報表的過程。

  1. 資料首先從Orders表和Customers表中提取。
  2. 透過連線操作將兩個表的資料合併。
  3. 合併後的結果集進一步用於生成最終的報表。
  4. 欄位級別的血統分析確保了報表中的每個欄位都能夠追溯到其原始來源。

隨著資料生態系統的不斷演進,資料血統分析技術也將持續進步。未來,我們可以期待看到更多自動化和智慧化的資料血統分析工具,這些工具將進一步簡化資料管理和分析的過程,提高資料的可靠性和可用性。同時,資料血統分析也將與其他資料管理技術(如資料治理、資料品質管理和資料安全)更加緊密地整合,共同推動資料驅動的業務創新和發展。

現代資料系統中端對端欄位級別沿襲的建構

在現代資料系統中,欄位級別沿襲(Field-Level Lineage)的建構是一項極具挑戰性的任務。由於SQL子句組合的數量龐大,幾乎不可能涵蓋所有可能的情況。事實上,在為Monte Carlo的客戶構建欄位級別沿襲時,最初的原型僅涵蓋了大約70%的可能組合。

欄位級別沿襲的基本結構

大多數沿襲的基本結構包含三個元素(圖7-3):

  • 目標表(Destination Table),儲存在下游報告中
  • 目標欄位(Destination Fields),儲存在目標表中
  • 源表(Source Tables),儲存在資料倉儲中

沿襲模型的建立

為了捕捉多個查詢之間的關係,我們建議使用邏輯資料模型、table_mcon ID和雜湊欄位級別沿襲物件共同作為檔案的ID。對於相同的目標表,可能會有多個不同的查詢來更新它。使用目標表的mcon和雜湊欄位級別沿襲物件,可以捕捉到給定目標表的所有不同沿襲組合。

示例7-1:目標表(分析報告)和一個或多個源表之間的沿襲查詢

{
  "edge_id": "37d65dc5c943cab124398b2c43f0d8f2c0ff5e76a2ba3052",
  "account_id": "ee7c21ae-9af9-4ce0-ac51-fa953065d6f7",
  "version": "normalized_v0.25",
  "job_ts": "2021-08-06 18:51:02.439000",
  "expire_at": "2021-08-13 18:51:02.439000",
  "destination_table_mcon": "", // 目標表的mcon
  "source_table_mcons": [
    "", // mcon1
    ""  // mcon2
  ], // 新增目標表的mcon和源表的mcon
  "sources": [
    {
      "table_mcon": "", // 表的mcon
      "field_name": ""
    },
    ...
  ],
  "destination_field": "新欄位名稱",
  "created_time": "2021-08-06 06:29:44.341000",
  "last_update_time": "2021-08-06 18:51:02.439000",
  "last_update_user_id": null,
  "parsed_query": ""
}

內容解密:

  • edge_id:沿襲關係的唯一標識
  • account_id:賬戶ID
  • version:版本號
  • job_tsexpire_at:任務時間戳和過期時間
  • destination_table_mconsource_table_mcons:目標表和源表的mcon
  • sources:源欄位的詳細資訊
  • destination_field:目標欄位的名稱

沿襲模型的實際應用

在這個沿襲模型中,我們有一個目標表。對於目標表中的每個欄位,都有一個源表和源欄位的列表來定義該欄位,被稱為選定欄位(Selected Fields)。該模型還包含另一個源表和欄位的列表,包含非選定欄位(Nonselected Fields)。

示例7-2:簡化複雜查詢的JSON查詢

CREATE OR REPLACE TABLE decom.usage_timelines.pdt_usage_activities AS (
  WITH usage_stuck_to_be_processed AS (
    SELECT s.usage_id,
           s.created_date
    FROM 'decom.processed.subscriptions' s
    JOIN 'decom.processed.usages' u
    ON s.usage_id = u.id
    WHERE (s.state = 'to_be_processed' AND u.activated_at IS NOT NULL)
  ),
  usage_subscription_state_updated as (
    SELECT *,
           rank() OVER (PARTITION BY usage_id ORDER BY created_at desc) AS sub_update_no_desc
    FROM 'decom.usage_timelines.usage_subscription_states' al_s
  ),
  usages_batch_removeled as (
    select distinct u.id as usage_id
    from 'decom.processed.usages' u
    join 'decom.processed.subscriptions' s on u.id = s.usage_id
    left join usage_subscription_state_updated ussu on ussu.usage_id = u.id and ussu.sub_update_no_desc = 1
    where s.state = 'in_question' and ussu.to_value = 'active'
  ),
  usage_subscription_state_change_actions AS (
    SELECT ussu.usage_id AS usage_id,
           CASE
             WHEN (
               ussu.from_value = 'to_be_processed' AND
               ussu.to_value = 'active' AND
               sub_update_no = 1
             ) THEN 'activate subscription'
             WHEN (
               ussu.from_value = 'to_be_processed' AND
               ussu.to_value IN ('in_question', 'disabled') AND
               sub_update_no = 1
             ) THEN 'remove from to_be_processed'
             ...
           END AS action,
           ussu.created_at AS action_at
    FROM decom.usage_timelines.usage_subscription_states ussu
  ),
  lead_subscription_orders AS (
    SELECT usage_id AS usage_id,
           CASE
             WHEN order_type = 'lead'
             THEN CAST('lead_order' AS string)
             WHEN order_type = 'regular'
             THEN CAST('regular_order' AS string)
           END AS action,
           MIN(order_placed_at) AS action_at
    FROM 'decom.cart.usages_orders_process'
    WHERE usage_legit_order_no = 1 AND order_placed_at IS NOT NULL
    GROUP BY 1, 2
  ),
  lead_subscription_order_send_dates AS (
    SELECT usage_id AS usage_id,
           ...
  )
  ...
)

內容解密:

  • 使用WITH子句定義多個臨時表,例如usage_stuck_to_be_processedusage_subscription_state_updated
  • 在主查詢中使用這些臨時表進行連線和資料處理
  • 使用CASE陳述式進行條件判斷和資料轉換
  • 使用視窗函式rank()進行資料排名

圖表翻譯:

此圖示展示了源表和目標表之間的欄位級別沿襲關係。