首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >当特征成为技术债:一次线上事故引发的思考

当特征成为技术债:一次线上事故引发的思考

作者头像
DEEPSAPCE MATRIX
发布2026-02-27 10:48:59
发布2026-02-27 10:48:59
960
举报
文章被收录于专栏:HUMAN3.0HUMAN3.0

本文涉及的指标、时间、表名与人员信息均已脱敏或合成,不对应任何具体业务。

凌晨三点四十七分,我被电话吵醒。

"模型 CTR 掉了 15 个点,现在全在查原因。"值班同学的声音透着焦虑。

我挣扎着坐起来,打开笔记本。监控大盘上,红色的告警曲线像心电图失常一样触目惊心:

代码语言:javascript
复制
核心指标:CTR 从 3.2% 骤降至 2.7%(下降 15.6%)
影响范围:全量用户
当前时间:03:47 AM
距离早高峰:3小时13分钟

这是我入职以来遇到的最严重的线上故障。更糟糕的是,距离早高峰流量只剩 3 个小时了。

常规排查:一切正常?

按照应急手册,我们快速过了一遍常规检查项:

  • ✅ 模型服务是否正常?→ 正常,QPS 稳定
  • ✅ 特征服务是否异常?→ 正常,P99 延迟在阈值内
  • ✅ 最近是否有模型发布?→ 无,最近发布在 3 天前
  • ✅ 是否有大促或流量突增?→ 无异常流量

所有监控指标都显示"一切正常",但 CTR 就是在掉。这是最让人崩溃的情况:系统说正常,但业务指标在暴跌

凌晨四点十分,有人在群里发了一张截图:"我看了下特征分布,user_7d_click_rate 好像不太对。"

我立刻打开特征监控,心一沉。

正常情况下,这个特征的分布应该是:

  • 均值:0.15
  • 标准差:0.08
  • 大部分用户在 0.05 ~ 0.25 之间

但此刻的数据显示:90% 的用户该特征值为 0

找到了!是特征数据出了问题。

但新的问题来了:这个特征是谁算的?

定位之困:这个特征是谁算的?

我打开特征平台的管理界面,搜索 user_7d_click_rate,找到了特征的定义:

json

代码语言:javascript
复制
{
  "feature_name": "user_7d_click_rate",
  "description": "用户近7天点击率",
  "type": "Float",
  "created_at": "2023-05-12",
  "created_by": "@zhangsan"
}

看起来很清楚,对吧?但是,这个特征是在哪里被计算出来的?存在哪张表里?上游依赖是什么?

这些关键信息,全部空白。

我看了眼创建人:@zhangsan。心里咯噔一下,这位同学三个月前离职了。

凌晨四点二十分,我们开始了漫长的排查之旅。

第一步:翻 Git 提交记录(20分钟)

我在代码仓库里搜索关键词 user_7d_click_rate,找到了 3 个相关提交:

  • model-training 仓库:模型训练时使用这个特征
  • feature-service 仓库:特征服务读取这个特征
  • data-pipeline 仓库:某个 SQL 里引用了这个特征

都是"使用"这个特征,没有一个是"产出"这个特征的。

第二步:翻 DAG(30分钟)

我们有 200 多个数据 DAG,我只能靠 DAG 名称猜测可能的任务:

  • user_profile_daily ?看起来像
  • user_behavior_agg ?也有可能
  • feature_etl_hourly ?不确定

我点开 user_profile_daily,翻了 500 行 Spark 代码,终于在某个角落找到了:

python

代码语言:javascript
复制
# 计算用户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 表血缘(15分钟)

我用 Hive 的元数据查询工具,追溯 feature_db.user_features_daily 这张表的来源,发现它的上游数据源是 ods.user_click_log

我查了下这张表的分区:

sql

代码语言:javascript
复制
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 分钟以内

但我们没有。

我们有的是:

  • 特征平台:记录了特征的元数据(名称、类型、描述)
  • 数据流:Airflow DAG、Spark Job、Flink 任务在跑
  • 但两者之间,没有映射关系

这就像 API 文档和实现代码分离,但没有任何方式能从文档跳转到代码,也无法从代码生成文档。

代码语言:javascript
复制
特征注册(定义)              数据流(产出)
     ↓                           ↓
  特征名                         DAG
  类型                        Spark Job
  描述                        计算逻辑
  创建人                      上游依赖
     ↓                           ↓
    ???  <-- 没有关联 -->  ???

我打开特征平台,看着那 1200+ 个特征,突然意识到:特征已经成为了我们的技术债

特征失控的三个阶段

回顾我们团队的历程,特征管理经历了三个阶段:

阶段1:野蛮生长(2022年)

那时候我们只有不到 100 个特征,大家都知道特征是谁算的。新人来了,老员工口头讲解一遍就能上手。

管理方式:人肉记忆 + 一个 Excel 表格。

问题还不明显。

阶段2:失控(2024年,也就是现在)

特征数量突破 1000 个。问题开始出现:

  • 同一个语义的特征有 3 个版本
  • user_click_rate(v1,已废弃但还在跑)
  • user_7d_click_rate(v2,当前使用)
  • user_click_rate_v3(v3,正在灰度)
  • 新人:我该用哪个?
  • 特征创建者离职了
  • 没人知道计算逻辑为什么是这样
  • 没人敢改,怕改出问题
  • 僵尸特征
  • 某些特征已经 6 个月没人用了
  • 但 Airflow 任务还在每天跑
  • 浪费资源,无人知晓

管理方式:特征平台 + 文档(但文档更新不及时)。

问题频繁出现,今天这次只是冰山一角。

阶段3:技术债(未来?)

如果不改变,我们会走向:

  • 特征数量 > 5000 个
  • 无人敢改任何数据源(不知道会影响哪些特征)
  • 无人敢下线任何特征(不知道谁在用)
  • 故障定位时间越来越长
  • 新人上手周期从 2 周变成 2 个月

为什么传统方法解决不了

你可能会说:这不就是管理问题吗?写好文档、规范命名不就行了?

我们试过:

❌ 文档更新不及时,代码改了文档没改。三个月后,文档和现实脱节。

  1. 靠文档?

❌ 人总会犯错,规范难以强制执行。而且离职的人多了,规范也就没人遵守了。

  1. 靠命名规范?

❌ 注释分散在 10 个仓库、200 个文件里,无法全局检索。

  1. 靠代码注释?

❌ 特征数量一多就维护不过来,而且总有遗漏。

  1. 靠人肉维护映射表?

我们不缺文档,我们缺的是让文档自动生成的机制。

如果有特征血缘系统

让我们做个思想实验:如果那天凌晨,我们有一个特征血缘系统,会发生什么?

平行宇宙中的故障处理流程

1. 凌晨 3:47

- 收到 CTR 告警

我打开血缘系统,搜索 user_7d_click_rate,立刻看到:

2. 凌晨 3:50(3分钟)

代码语言:javascript
复制
   特征: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. 变更告警

我们的探索之旅

那次故障之后,我们决定不能再这样下去了。我们需要一个特征血缘系统。

于是,我们启动了一个项目,目标很明确:让每一个特征的来龙去脉都清清楚楚

技术选型上,我们选择了:

  • MySQL:存储元数据(数据集、任务、运行记录)
  • xgraph:公司已有的图数据库,用于血缘关系的图查询
  • OpenLineage:业界标准的血缘事件规范

我们借鉴了 Marquez 的设计思想,但适配了自己的技术栈。

这个专栏会记录我们的完整过程:

  • 如何设计特征血缘系统的架构?
  • 如何从 Spark、Airflow 中采集血缘事件?
  • 如何用图数据库实现血缘查询?
  • 如何推动团队使用血缘系统?
  • 遇到了哪些坑,如何解决?

这不是一篇技术文档,而是一个真实的工程故事。

下一篇,我会讲:特征血缘和数据血缘有什么区别?为什么特征血缘不能简单等同于数据血缘?

写在最后

凌晨的那次故障,让我们付出了惨痛的代价。但它也让我们看清了一个事实:

随着特征数量的增长,传统的人肉管理方式已经不够用了。我们需要系统化、自动化的特征治理能力。

特征血缘只是第一步。它不会解决所有问题,但它是特征治理的基础设施。

如果你的团队也有几百上千个特征,如果你也曾在凌晨为定位问题而焦头烂额,如果你也想让特征管理更加清晰可控——

欢迎关注这个专栏。我会把我们的经验、教训、思考,毫无保留地分享给你。


你是否也遇到过类似的问题?你的团队是如何管理特征的?欢迎在评论区分享你的经历。

如果觉得这篇文章对你有帮助,欢迎点赞、转发,让更多算法同学看到。

下期预告:《特征血缘不是数据血缘:厘清两个容易混淆的概念》


关于《特征治理笔记》

这是一个记录特征血缘系统从 0 到 1 建设过程的技术专栏,面向算法工程师、模型平台工程师、数据工程师。

专栏会覆盖:特征血缘的设计与实现、特征治理的最佳实践、模型平台工程的思考。

不追热点,只写真实的工程实践。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深空矩阵 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 常规排查:一切正常?
  • 定位之困:这个特征是谁算的?
    • 第一步:翻 Git 提交记录(20分钟)
    • 第二步:翻 DAG(30分钟)
    • 第三步:查 Hive 表血缘(15分钟)
    • 第四步:等人(???分钟)
  • 问题的本质:定义与产出的脱节
  • 特征失控的三个阶段
    • 阶段1:野蛮生长(2022年)
    • 阶段2:失控(2024年,也就是现在)
    • 阶段3:技术债(未来?)
  • 为什么传统方法解决不了
  • 如果有特征血缘系统
  • 特征血缘到底是什么
  • 血缘系统能做什么
  • 我们的探索之旅
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档