
本文涉及的指标、时间、表名与人员信息均已脱敏或合成,不对应任何具体业务。
凌晨三点四十七分,我被电话吵醒。
"模型 CTR 掉了 15 个点,现在全在查原因。"值班同学的声音透着焦虑。
我挣扎着坐起来,打开笔记本。监控大盘上,红色的告警曲线像心电图失常一样触目惊心:
核心指标:CTR 从 3.2% 骤降至 2.7%(下降 15.6%)
影响范围:全量用户
当前时间:03:47 AM
距离早高峰:3小时13分钟这是我入职以来遇到的最严重的线上故障。更糟糕的是,距离早高峰流量只剩 3 个小时了。
按照应急手册,我们快速过了一遍常规检查项:
所有监控指标都显示"一切正常",但 CTR 就是在掉。这是最让人崩溃的情况:系统说正常,但业务指标在暴跌。
凌晨四点十分,有人在群里发了一张截图:"我看了下特征分布,user_7d_click_rate 好像不太对。"
我立刻打开特征监控,心一沉。
正常情况下,这个特征的分布应该是:
但此刻的数据显示:90% 的用户该特征值为 0。
找到了!是特征数据出了问题。
但新的问题来了:这个特征是谁算的?
我打开特征平台的管理界面,搜索 user_7d_click_rate,找到了特征的定义:
json
{
"feature_name": "user_7d_click_rate",
"description": "用户近7天点击率",
"type": "Float",
"created_at": "2023-05-12",
"created_by": "@zhangsan"
}看起来很清楚,对吧?但是,这个特征是在哪里被计算出来的?存在哪张表里?上游依赖是什么?
这些关键信息,全部空白。
我看了眼创建人:@zhangsan。心里咯噔一下,这位同学三个月前离职了。
凌晨四点二十分,我们开始了漫长的排查之旅。
我在代码仓库里搜索关键词 user_7d_click_rate,找到了 3 个相关提交:
model-training 仓库:模型训练时使用这个特征feature-service 仓库:特征服务读取这个特征data-pipeline 仓库:某个 SQL 里引用了这个特征都是"使用"这个特征,没有一个是"产出"这个特征的。
我们有 200 多个数据 DAG,我只能靠 DAG 名称猜测可能的任务:
user_profile_daily ?看起来像user_behavior_agg ?也有可能feature_etl_hourly ?不确定我点开 user_profile_daily,翻了 500 行 Spark 代码,终于在某个角落找到了:
python
# 计算用户7天点击率
df_click_rate = (
df_clicks.groupBy("user_id")
.agg(
F.sum("click_cnt") / F.sum("show_cnt") as "rate"
)
)
# 写入特征表
df_click_rate.write.saveAsTable("feature_db.user_features_daily")但代码里没有明确标注这个字段对应的特征名是 user_7d_click_rate。我只能猜测,这个 rate 字段就是那个特征。
我用 Hive 的元数据查询工具,追溯 feature_db.user_features_daily 这张表的来源,发现它的上游数据源是 ods.user_click_log。
我查了下这张表的分区:
sql
SELECT * FROM ods.user_click_log WHERE dt = '2024-11-13';
-- 结果:0 rows找到了!凌晨 2:00 的分区数据是空的。
但为什么是空的?我又得去找数据采集任务的负责人。
凌晨四点四十分,我在工作群发了一条消息:
"@all 紧急求助!
ods.user_click_log今天凌晨2点的分区数据为空,是哪个采集任务负责的?"
然后,等待。
排查方式 | 耗时 | 结果 |
|---|---|---|
查特征平台 | 5 min | 找到特征定义,无产出信息 |
翻 Git | 20 min | 找到使用代码,未找到产出 |
翻 Airflow | 30 min | 找到疑似任务 |
查 Hive 血缘 | 15 min | 找到上游数据源 |
等人回复 | 20 min | 终于有人回 |
凌晨五点十五分,数据团队的同学回复了:是采集任务的一个配置错误,导致凌晨 2 点的数据没有写入。
修复很快,只用了 15 分钟。
从发现问题到定位根因,我们用了整整 90 分钟。而真正的修复,只用了 15 分钟。
故障恢复后,我坐在电脑前,思考刚才的混乱。
如果我们一开始就知道 user_7d_click_rate 是由 user_profile_daily 任务产出的,如果我们能一眼看到这个特征依赖的上游数据源 ods.user_click_log,这个故障的定位时间可以缩短到 10 分钟以内。
但我们没有。
我们有的是:
这就像 API 文档和实现代码分离,但没有任何方式能从文档跳转到代码,也无法从代码生成文档。
特征注册(定义) 数据流(产出)
↓ ↓
特征名 DAG
类型 Spark Job
描述 计算逻辑
创建人 上游依赖
↓ ↓
??? <-- 没有关联 --> ???我打开特征平台,看着那 1200+ 个特征,突然意识到:特征已经成为了我们的技术债。
回顾我们团队的历程,特征管理经历了三个阶段:
那时候我们只有不到 100 个特征,大家都知道特征是谁算的。新人来了,老员工口头讲解一遍就能上手。
管理方式:人肉记忆 + 一个 Excel 表格。
问题还不明显。
特征数量突破 1000 个。问题开始出现:
user_click_rate(v1,已废弃但还在跑)user_7d_click_rate(v2,当前使用)user_click_rate_v3(v3,正在灰度)管理方式:特征平台 + 文档(但文档更新不及时)。
问题频繁出现,今天这次只是冰山一角。
如果不改变,我们会走向:
你可能会说:这不就是管理问题吗?写好文档、规范命名不就行了?
我们试过:
❌ 文档更新不及时,代码改了文档没改。三个月后,文档和现实脱节。
❌ 人总会犯错,规范难以强制执行。而且离职的人多了,规范也就没人遵守了。
❌ 注释分散在 10 个仓库、200 个文件里,无法全局检索。
❌ 特征数量一多就维护不过来,而且总有遗漏。
我们不缺文档,我们缺的是让文档自动生成的机制。
让我们做个思想实验:如果那天凌晨,我们有一个特征血缘系统,会发生什么?
平行宇宙中的故障处理流程:
1. 凌晨 3:47
- 收到 CTR 告警
我打开血缘系统,搜索 user_7d_click_rate,立刻看到:
2. 凌晨 3:50(3分钟)
特征:user_7d_click_rate
├── 产出任务:user_profile_daily(Airflow)
├── 上游依赖:ods.user_click_log
├── 计算逻辑:sum(click)/sum(show) over 7 days
└── 下游使用:ctr_model_v3, rank_model_v2 等5个模型点击上游数据源 ods.user_click_log,发现凌晨 2:00 分区数据为空
3. 凌晨 3:55(5分钟)
联系数据团队,回溯数据,重跑任务
4. 凌晨 4:00(5分钟)
特征恢复正常,CTR 回升
5. 凌晨 5:00
时间对比:
阶段 | 无血缘系统 | 有血缘系统 | 节省时间 |
|---|---|---|---|
定位特征产出任务 | 90 min | 3 min | 87 min |
检查上游数据 | 15 min | 5 min | 10 min |
修复 | 15 min | 15 min | 0 min |
总计 | 120 min | 23 min | 97 min |
你可能会问:特征血缘听起来很玄乎,到底是个什么东西?
让我用两个类比来解释:
类比1:代码的调用链
在 IDE 里,你可以右键点击一个函数,选择"查找所有调用"(Find Usages),立刻看到这个函数被哪些地方调用。
特征血缘 = 数据流的"Find Usages"。
类比2:快递的物流追踪
你可以追踪一个包裹从仓库 → 转运中心 → 配送站 → 你家的完整路径。
特征血缘 = 追踪一个特征从原始日志 → ETL 任务 → 特征表 → 模型的完整路径。
简单来说:
特征血缘是一个记录"特征从哪来、到哪去"的系统。它能回答: - 这个特征是由哪个任务产出的? - 这个特征依赖哪些上游数据源? - 这个特征被哪些模型使用? - 如果我改了上游数据,会影响哪些特征和模型?
血缘系统不是万能的,但它解决的是最基础、最核心的问题:建立数据流的可追溯性。
就像你不会在没有 Git 的情况下管理代码,你也不应该在没有血缘的情况下管理特征。
除了故障定位,特征血缘系统还能做很多事:
"如果我改了 user_click_log 的 schema,会影响哪些特征和模型?" → 血缘系统可以自动分析影响范围,生成影响报告
1. 影响分析
"哪些特征已经 6 个月没人用了?" → 自动识别僵尸特征,建议下线
2. 特征治理
"有没有多个任务在计算相同的特征?" → 发现重复计算,优化资源使用
3. 成本优化
"新人想了解推荐系统用了哪些特征?" → 一键查看模型的完整特征链路
4. 知识传承
"数据源 schema 变更时,自动通知下游特征和模型的负责人" → 从被动响应到主动预防
5. 变更告警
那次故障之后,我们决定不能再这样下去了。我们需要一个特征血缘系统。
于是,我们启动了一个项目,目标很明确:让每一个特征的来龙去脉都清清楚楚。
技术选型上,我们选择了:
我们借鉴了 Marquez 的设计思想,但适配了自己的技术栈。
这个专栏会记录我们的完整过程:
这不是一篇技术文档,而是一个真实的工程故事。
下一篇,我会讲:特征血缘和数据血缘有什么区别?为什么特征血缘不能简单等同于数据血缘?
凌晨的那次故障,让我们付出了惨痛的代价。但它也让我们看清了一个事实:
随着特征数量的增长,传统的人肉管理方式已经不够用了。我们需要系统化、自动化的特征治理能力。
特征血缘只是第一步。它不会解决所有问题,但它是特征治理的基础设施。
如果你的团队也有几百上千个特征,如果你也曾在凌晨为定位问题而焦头烂额,如果你也想让特征管理更加清晰可控——
欢迎关注这个专栏。我会把我们的经验、教训、思考,毫无保留地分享给你。
你是否也遇到过类似的问题?你的团队是如何管理特征的?欢迎在评论区分享你的经历。
如果觉得这篇文章对你有帮助,欢迎点赞、转发,让更多算法同学看到。
下期预告:《特征血缘不是数据血缘:厘清两个容易混淆的概念》
关于《特征治理笔记》
这是一个记录特征血缘系统从 0 到 1 建设过程的技术专栏,面向算法工程师、模型平台工程师、数据工程师。
专栏会覆盖:特征血缘的设计与实现、特征治理的最佳实践、模型平台工程的思考。
不追热点,只写真实的工程实践。