46.5 数据治理与血缘

一句话

血缘回答“改这张表/这一列,会炸到谁”:表级血缘用于评估影响面,列级血缘用于定位口径来源——没有血缘的治理,就是靠人问“这个字段谁在用”。

治理的四件事

事项 解决什么 最小可用做法
血缘 改动影响面、故障定位 解析 ETL/查询日志,建上下游图
指标字典 口径不一致 每个指标写“定义 + 公式 + 负责人 + 来源表”
质量校验 数据错了没人知道 行数/空值/枚举/波动阈值告警
权限与审计 谁能看、谁看过 按分级授权 + 访问日志(38.5)

代码:血缘图的爆炸半径

package main

import (
	"fmt"
	"sort"
)

// 表级血缘:from -> to
var edges = map[string][]string{
	"ods_orders":  {"dwd_orders", "dwd_order_item"},
	"ods_users":   {"dwd_users"},
	"dwd_orders":  {"dws_sales", "ads_report"},
	"dwd_order_item": {"dws_sales"},
	"dwd_users":   {"dim_user", "dws_user_value"},
	"dws_sales":   {"ads_report", "ml_feature_sales"},
	"dws_user_value": {"ads_report"},
	"dim_user":    {"ads_report", "ml_feature_user"},
	"ads_report":  {"dashboard_gmv", "dashboard_ops"},
	"ml_feature_sales": {"model_forecast"},
	"ml_feature_user":  {"model_churn"},
}

// 哪些节点是“最终消费方”
var consumers = map[string]bool{
	"dashboard_gmv": true, "dashboard_ops": true, "model_forecast": true, "model_churn": true,
}

func downstream(start string) (all []string, byLevel map[int][]string) {
	byLevel = map[int][]string{}
	seen := map[string]bool{start: true}
	level := map[string]int{start: 0}
	queue := []string{start}
	for len(queue) > 0 {
		cur := queue[0]
		queue = queue[1:]
		for _, nxt := range edges[cur] {
			if seen[nxt] {
				continue
			}
			seen[nxt] = true
			level[nxt] = level[cur] + 1
			byLevel[level[nxt]] = append(byLevel[level[nxt]], nxt)
			all = append(all, nxt)
			queue = append(queue, nxt)
		}
	}
	return
}

func main() {
	for _, src := range []string{"ods_orders", "dwd_users", "dws_sales"} {
		all, byLevel := downstream(src)
		cons := 0
		for _, n := range all {
			if consumers[n] {
				cons++
			}
		}
		fmt.Printf("=== 改 %s 会影响 %d 个下游节点、%d 个最终消费方 ===\n", src, len(all), cons)
		var levels []int
		for l := range byLevel {
			levels = append(levels, l)
		}
		sort.Ints(levels)
		for _, l := range levels {
			nodes := byLevel[l]
			sort.Strings(nodes)
			fmt.Printf("  %d 跳:%v\n", l, nodes)
		}
		fmt.Println()
	}
}

本机实测(macOS,Apple M1,Go 1.27):

=== 改 ods_orders 会影响 8 个下游节点、3 个最终消费方 ===
  1 跳:[dwd_order_item dwd_orders]
  2 跳:[ads_report dws_sales]
  3 跳:[dashboard_gmv dashboard_ops ml_feature_sales]
  4 跳:[model_forecast]

=== 改 dwd_users 会影响 7 个下游节点、3 个最终消费方 ===
  1 跳:[dim_user dws_user_value]
  2 跳:[ads_report ml_feature_user]
  3 跳:[dashboard_gmv dashboard_ops model_churn]

=== 改 dws_sales 会影响 5 个下游节点、3 个最终消费方 ===
  1 跳:[ads_report ml_feature_sales]
  2 跳:[dashboard_gmv dashboard_ops model_forecast]

读数:

源表 下游节点 最终消费方 结论
ods_orders 8 3 改它等于改全站报表 + 两个模型
dwd_users 7 3 影响用户维度、用户价值模型与流失模型
dws_sales 5 3 影响 GMV 看板与预测模型

🔑 血缘的价值不是“画图”,而是变更前的检查清单:改 ods_orders 的字段 → 自动列出 8 个下游 + 3 个消费方 → 通知负责人 → 排期。没有这张图,只能靠“谁记得”。

列级血缘:口径问题的唯一解

问题 表级血缘能否回答 列级血缘
改表会影响哪些任务 ✅ —
这个指标的“销售额”来自哪张表的哪列 ❌ ✅
两个报表的同名指标为什么不一样 ❌ ✅(追到不同来源列/不同过滤条件)

⚠️ 列级血缘的实现成本高(要解析 SQL AST),但这是“口径争议”唯一能给出证据的东西。折中做法:先覆盖核心指标(GMV、DAU、转化率)的列级血缘,其余保持表级。

反模式

反模式 后果
血缘靠人工维护文档 三个月后与真实链路脱节
只有表级血缘,没有列级 指标口径争议无法定位
改表前不查血缘 上线后下游报表全空
指标没有负责人 口径错了没人认领
质量校验只看行数 行数正常但金额算错,照样上线
血缘图不包含“外部依赖”(API/文件) 影响面漏一半

自检

Q1 为什么表级血缘不够,还需要列级血缘?

▸ 答案:表级只回答“这张表的下游是谁”,回答不了“这个指标用了哪一列、经过什么过滤”。口径问题(同名指标不同值)往往源于列级差异:一个用 amount,另一个用 amount - refund;一个含税一个不含。只有列级血缘能追到源头,给出“为什么不同”的证据。

Q2 血缘图怎么自动生成,而不是靠人填?

▸ 答案:从执行记录里解析:① 数据仓库的查询日志/执行计划(解析 SQL 的源表与目标表);② ETL 调度系统的任务定义(输入/输出表);③ 存储过程的 AST 解析。列级血缘需要 SQL 解析器(如 sqlparse/Calcite),成本更高。关键是“自动 + 定期刷新”,人工维护必然过期。

Q3 一个有 200 张表、500 个任务的数仓,血缘最该先覆盖什么?

▸ 答案:先覆盖核心指标链路(GMV、订单量、DAU 等 10–20 个)和被引用最多的表(入度最高的 20 张)。这两类覆盖了 80% 的变更风险与口径争议;长尾表可以只做表级。判断依据是“出问题时会不会有人问‘这数从哪来的’”。

关联