🏠 总目录📚 本教程 05 · 该监控什么
📑 本页目录(点开跳转)

05 · 该监控什么

40 分钟 | ⭐ 三层监控体系


🎯 一句话

大部分团队只监控了服务是否存活,而模型的问题几乎从不表现为服务挂掉。 你需要三层监控:系统层、数据层、模型层 —— 而后两层才是这一章的重点。

③ 模型层预测分布、置信度、业务指标「它给的答案还合理吗」最少人做② 数据层特征分布、缺失率、取值范围「喂进去的还对吗」性价比最高① 系统层QPS、延迟、错误率、资源「服务还活着吗」大家都有模型出问题时,系统层几乎总是正常的只监控最底下那层,等于没监控模型
大部分团队只做了最底下那层。⭐ 但模型出问题时,系统层几乎总是正常的 —— 服务照样 200 OK、延迟照样 20ms,只是答案悄悄变差了。

🏛️ 一、三层监控

③ 模型层 预测分布、置信度、业务指标
  • 「它给出的答案还合理吗」
② 数据层 特征分布、缺失率、取值范围
  • 「喂进去的东西还对吗」
① 系统层 QPS、延迟、错误率、资源
  • 「服务还活着吗」

🔑 一个反直觉的事实模型出问题时,系统层几乎总是正常的。 特征算错了、上游埋点变了、分布漂移了 —— 服务照样 200 OK、延迟照样 20ms。 只监控第①层,等于没监控模型。


① 系统层(简单,但有两个模型特有的点)

指标 阈值参考
QPS、错误率、P50/P99 延迟 按 SLA 定
CPU / 内存 / GPU 利用率
模型加载耗时 / 版本号 部署后确认版本真的变了
降级触发次数 ⭐⭐ >0 就该看

⚠️ 降级次数是最容易漏的一个指标第 3 章提过): 系统降级时不报错,它「正常工作」着。不监控它,可能几个月都不知道模型根本没在跑。


② 数据层:性价比最高的一层 ⭐⭐

对每个进入模型的特征,监控四件事

   1. 缺失率        —— 突然从 2% 涨到 40%?上游挂了
   2. 取值范围      —— 出现了训练时没见过的极值?单位可能变了
   3. 分布          —— 均值/分位数漂移(第 6 章详讲)
   4. 类别集合      —— 出现新类别?消失了某个类别?⭐

🔨 一个够用的实现

import numpy as np, pandas as pd, json

def feature_profile(df: pd.DataFrame) -> dict:
    """训练时算一次存下来,线上定期算同样的指标做对比"""
    prof = {}
    for col in df.columns:
        s = df[col]
        d = {"missing_rate": float(s.isna().mean())}
        if np.issubdtype(s.dtype, np.number):
            q = s.quantile([.01, .25, .5, .75, .99]).to_dict()
            d.update(kind="num", mean=float(s.mean()), std=float(s.std()),
                     **{f"p{int(k*100)}": float(v) for k, v in q.items()})
        else:
            vc = s.value_counts(normalize=True)
            d.update(kind="cat", n_unique=int(s.nunique()),
                     top=vc.head(20).to_dict())          # ⭐ 存 top-K 占比
        prof[col] = d
    return prof


def check(base: dict, live: dict, missing_jump=0.1, mean_sigma=3):
    """返回需要告警的特征。规则简单但在实践中够用。"""
    alerts = []
    for col, b in base.items():
        l = live.get(col)
        if l is None:
            alerts.append((col, "特征消失了")); continue
        if l["missing_rate"] - b["missing_rate"] > missing_jump:
            alerts.append((col, f"缺失率 {b['missing_rate']:.1%}{l['missing_rate']:.1%}"))
        if b["kind"] == "num" and b["std"] > 0:
            z = abs(l["mean"] - b["mean"]) / b["std"]        # ⭐ 用训练集 std 归一
            if z > mean_sigma:
                alerts.append((col, f"均值漂移 {z:.1f}σ"))
        if b["kind"] == "cat":
            new = set(l["top"]) - set(b["top"])
            if new:
                alerts.append((col, f"新类别 {list(new)[:3]}"))
    return alerts

关键设计用训练集的 std 做归一化,而不是线上自己的 std。 否则线上分布整体平移时,用它自己的 std 算 z 分数会看不出异常。

⚠️ 数据层监控的三个坑

说明
只监控入模特征,不监控上游原始字段 特征是算出来的,原始字段变了但特征看起来正常是可能的
按天聚合,掩盖了小时级异常 上游挂了 3 小时,按天看均值只动了一点
告警阈值一刀切 有的特征天然波动大(如「距上次访问时长」),要分别定阈值

③ 模型层:它给的答案还合理吗

   1. 预测值分布      —— 均值、方差、分位数
                        ⭐ 突变几乎总是坏消息
   2. 预测的置信度     —— 高置信样本占比下降 = 模型开始「不确定」
   3. 输出多样性       —— 推荐/生成场景:是不是塌缩到少数几个答案
   4. 业务指标         —— CTR、转化率、人均时长
   5. 有标签后的真实指标 —— AUC、准确率(但会延迟,见第 7 章)

💡 预测分布突变的三种典型形态

   ① 整体平移      → 多半是某个特征的单位/口径变了
   ② 方差塌缩      → 模型开始给所有人差不多的分数
                     常见于特征大面积缺失(退化成只用偏置)⭐
   ③ 双峰变单峰    → 某一类样本消失了,或被错误地归到一起

第 ② 种特别值得记特征大面积缺失时,模型不会报错, 它会退化成「只用还剩下的那些特征 + 偏置」预测 —— 表现就是所有人的分数都挤在一起监控预测值的标准差,能第一时间发现它。


🎯 二、怎么定阈值(不用统计学也能做)

   ❌ 拍脑袋定「均值不能变超过 10%」
   ✅ 用历史数据反推:

   ① 取过去 30 天的每日指标
   ② 算它自己的均值和标准差
   ③ 阈值 = 均值 ± 3σ(或用分位数:超出历史 P1~P99)
   ④ 用剩下的历史数据回测:这个阈值会报多少次警?

   ⭐ 第 ④ 步最关键 —— 如果回测显示一个月要报 50 次警,
      这个阈值一定会被无视(第 8 章)

📋 三、一份最小可用的监控清单

如果时间只够做 8 个指标,做这 8 个

# 指标 为什么是它
1 错误率 / P99 延迟 系统 基本盘
2 降级触发次数 系统 静默失效的唯一信号 ⭐
3 模型版本号 系统 确认部署真的生效了
4 每个特征的缺失率 数据 上游故障最快的信号 ⭐⭐
5 Top-5 重要特征的均值漂移 数据 抓大放小
6 预测值的均值和标准差 模型 标准差塌缩=特征大面积缺失 ⭐
7 核心业务指标(按小时) 模型 最终目标
8 训练/线上特征一致性抽检 数据 第 4 章那个问题 ⭐⭐

💡 注意第 5 条「Top-5 重要特征」: 不用监控全部 200 个特征 —— 按重要性排序,盯住最重要的那几个, 覆盖了绝大部分风险,告警量还可控。 🔗 特征重要性怎么算、什么时候会骗你 → 第 9 章


🕰️ 四、被漏掉的第四类:新鲜度

前面三层都在问"值对不对"。还有一个问题几乎没人监控:这个值是什么时候算的。

   特征分布 ✅ 正常
   缺失率   ✅ 正常
   取值范围 ✅ 正常

   但它是【三天前】算出来的                💀
   → 所有分布检查都会通过,因为三天前的分布本来就是对的

要监控的三个新鲜度

新鲜度 定义 典型阈值
特征新鲜度 特征值的计算时间距现在多久 实时特征 < 5 分钟;T+1 特征 < 30 小时
上游表新鲜度 依赖的每张表最后一个分区的时间 按调度周期 + 1.5 倍冗余
模型新鲜度 当前线上模型的训练截止时间 按重训周期定(第 16 章)
# ⭐ 一个直接可跑的新鲜度检查:只查元数据,不扫全表,几乎零成本
import datetime

FRESHNESS_SLA = {                    # 表名 -> 允许的最大滞后(小时)
    "dw_user_action_di": 30,         # T+1 日表,给 6 小时冗余
    "rt_user_profile":  0.25,        # 实时表,15 分钟
    "dim_item_info":    72,          # 维表,3 天
}

def check_freshness(get_latest_partition_ts):
    now, alerts = datetime.datetime.now(), []
    for tbl, sla_h in FRESHNESS_SLA.items():
        ts = get_latest_partition_ts(tbl)             # ⭐ 只读元数据
        lag = (now - ts).total_seconds() / 3600
        if lag > sla_h:
            # ⭐ 直接给出「已经用旧数据服务了多久」,而不是只说"表旧了"
            alerts.append(f"{tbl} 滞后 {lag:.1f}h(SLA {sla_h}h),超出 {lag - sla_h:.1f}h")
    return alerts

为什么这一项性价比极高: 它是唯一能在"坏数据进入模型之前"就报警的检查 —— 分布监控要等坏数据流进来才看得见,新鲜度检查在上游没跑完的那一刻就知道。 🔗 第 1 章事故 B(假期调度没跑、被当成"假期效应")—— 一条新鲜度告警就能挡掉那两周的浪费。


📉 五、聚合粒度:一个能让监控完全失效的选择

💀 真实事故:所有监控都在,但什么都没报

   某推荐服务的用户画像表,凌晨 2:00–5:00 因上游故障返回空
   → 这 3 小时里,用户侧特征全部走默认值
   → 这 3 小时的 CTR 从 4.2% 掉到 1.1%(−74%)

   监控为什么没报:
   · 特征缺失率是【按天】算的
     全天缺失率 = 3/24 × 100% + 21/24 × 2% ≈ 14.3%
     告警阈值是「缺失率涨幅 > 10 个百分点」
     → 14.3% − 2% = 12.3pp  →  刚好触发了?

   ⚠️ 并没有:告警是【第二天早上 8 点】跑批时才算出来的
   → 距离故障开始已经 30 小时,距离结束 27 小时
   → 值班同学看到时,问题早就自己好了 → 标成"已恢复,不处理"
粒度 那 3 小时的 CTR 在图上长什么样 会不会被发现
按天 4.2% → 3.8%(−9%) ❌ 在正常波动内
按小时 4.2% → 1.1%(−74%) 一眼就看见
按 5 分钟 同上,但噪声更大 ✅ 但误报会变多

一条实用的经验法则监控的时间粒度,要比你能容忍的故障时长更细。 能容忍 1 小时故障 → 至少按 10 分钟聚合;能容忍 1 天 → 按小时。 按天聚合的监控,只能发现持续好几天的问题。

⚠️ 数据层监控的另外三个坑(补充第②节那三个):

后果 怎么修
只存聚合值,不存直方图 均值没变但形状变了(双峰→单峰),完全看不出 存分位数或固定分桶计数 ⭐
基线是"上周同期"而不是训练集 慢漂移会被一路跟随,永远不报警 💀 固定基线用训练集,同比只用来做告警去周期
监控用的是采样后的日志 采样率变了,指标跟着变,误以为是漂移 记录采样率,指标要除以它

第二条最隐蔽:如果基线永远是"上周",那么每周漂移 2% 的特征永远不会告警, 而一年后它已经漂了 65%。 正确做法:同时看两个基线 —— 训练集(判断"离训练分布多远")和上周(判断"最近有没有突变")。

💾 监控本身的成本(一个常被忽略的账)

做法 存储量级(200 特征 / 日均 1 亿请求) 备注
全量落特征日志 ~TB / 天 最全,但很多团队负担不起
按小时聚合 + 分位数 ~MB / 天 性价比最高,够用
抽样 1% 落原始特征 ~10 GB / 天 用于逐字段比对和排查 ⭐

💡 推荐组合聚合指标全量算(便宜)+ 原始特征抽样 1% 落盘(用于排查)。 前者负责"发现异常",后者负责"查清原因" —— 两件事的数据需求完全不同。


🔗 六、和站内其他章的关系

相关的地方 和这一章的关系
第 3 章降级监控 系统层第 4 项
第 4 章一致性 清单第 8 项
全景导论 13 LLMOps LLM 场景的监控
推荐算法 15 推荐场景的监控项

✅ 检查点

  1. 三层监控分别是什么?为什么说只做第一层等于没监控模型?
  2. 系统层最容易漏的模型特有指标是哪个?为什么?
  3. 数据层监控哪四件事?
  4. 为什么归一化要用训练集的 std 而不是线上的?
  5. 数据层监控的三个坑是什么?
  6. 预测值方差塌缩通常意味着什么?
  7. 定阈值时最关键的是哪一步?
  8. 为什么只监控 Top-5 重要特征就够?
  9. 「新鲜度」为什么是分布监控查不出来的?要监控哪三个新鲜度?为什么说它性价比极高?
  10. 那个 3 小时故障为什么所有监控都没报出来?按天和按小时看差别有多大?
  11. 「监控的时间粒度」该怎么定?
  12. 为什么基线不能只用"上周同期"?正确做法是什么?
  13. 监控数据该怎么存才划算?
👀 答案
  1. 系统层(还活着吗)、数据层(喂进去的还对吗)、模型层(答案还合理吗)。只做第一层等于没监控模型,因为模型出问题时系统层几乎总是正常的——特征算错、埋点变了、分布漂移,服务照样 200 OK、延迟正常。
  2. 降级触发次数。因为降级时不报错、"正常工作"着,不监控它可能几个月都不知道模型根本没在跑。
  3. 缺失率、取值范围、分布、类别集合(新类别/消失的类别)。
  4. 因为线上分布整体平移时,用它自己的 std 算 z 分数会看不出异常——基准必须是固定的训练集。
  5. ①只监控入模特征不监控上游原始字段 ②按天聚合掩盖小时级异常(上游挂 3 小时,按天看均值只动一点)③告警阈值一刀切。
  6. 特征大面积缺失。模型不报错,会退化成"只用剩下的特征+偏置"预测,表现就是所有人分数挤在一起。监控预测值标准差能第一时间发现。
  7. 用剩下的历史数据回测这个阈值会报多少次警。如果一个月报 50 次,它一定会被无视。
  8. 因为按重要性排序盯住最重要的几个,能覆盖绝大部分风险,同时告警量可控——不用监控全部 200 个特征。
  9. 因为三天前算出来的值,它的分布本来就是对的——所有分布检查都会通过。三个新鲜度:特征新鲜度(值的计算时间距现在多久)、上游表新鲜度(每张依赖表最后一个分区的时间)、模型新鲜度(训练截止时间)。性价比高是因为它是唯一能在坏数据进入模型之前就报警的检查——分布监控要等坏数据流进来才看得见。
  10. 因为缺失率是按天算的、而且第二天早上 8 点才跑批:全天缺失率被稀释成 14.3%,且告警产出时距故障开始已 30 小时、结束 27 小时,值班同学看到时问题已自己好了。按天看 CTR 只从 4.2% 掉到 3.8%(−9%,在正常波动内),按小时看是 4.2%→1.1%(−74%),一眼就看见
  11. 监控的时间粒度要比你能容忍的故障时长更细:能容忍 1 小时故障就至少按 10 分钟聚合,能容忍 1 天就按小时。按天聚合的监控只能发现持续好几天的问题。
  12. 因为慢漂移会被一路跟随,永远不报警——每周漂 2% 的特征一年后已经漂了 65%,但每周环比都正常。正确做法:同时看两个基线——训练集(判断"离训练分布多远")和上周(判断"最近有没有突变")。
  13. 聚合指标全量算(按小时 + 分位数,MB 量级)+ 原始特征抽样 1% 落盘(用于排查)。前者负责"发现异常",后者负责"查清原因",两件事的数据需求完全不同。全量落特征日志是 TB/天,多数团队负担不起。

🛑 可以停在这里

走神救援

三层监控:系统层(还活着吗)/ 数据层(喂进去的还对吗)/ 模型层(答案还合理吗)。⭐⭐反直觉的关键事实:模型出问题时系统层几乎总是正常的——特征算错、埋点变了、分布漂移,服务照样200 OK延迟20ms,只监控系统层等于没监控模型。系统层最容易漏的是降级触发次数(降级不报错、"正常工作"着)。数据层性价比最高,监控四件事:缺失率、取值范围、分布、类别集合;⭐归一化要用训练集的std(否则线上整体平移时看不出异常)。三个坑:只监控入模特征不看上游原始字段、按天聚合掩盖小时级异常、阈值一刀切。模型层看预测分布,⭐方差塌缩=特征大面积缺失(模型退化成只用偏置,所有人分数挤在一起)。⭐定阈值最关键的一步是用历史数据回测会报多少次警——一个月50次的阈值一定被无视。最小8个指标里最该做的:降级次数、每特征缺失率、Top-5特征均值漂移、预测值均值和标准差、训练/线上一致性抽检。⭐被漏掉的第四类:新鲜度——分布全对但值是三天前算的,所有分布检查都会通过;要看特征新鲜度 / 上游表新鲜度 / 模型新鲜度,它是唯一能在坏数据进入模型之前就报警的检查。💀聚合粒度能让监控完全失效:3 小时上游故障,按天看 CTR 只掉 9%(正常波动内),按小时看是 −74%;而且按天的告警第二天 8 点才算出来,看到时问题已自己好了。⭐法则:监控粒度要比你能容忍的故障时长更细(能容忍 1 小时就按 10 分钟聚合)。三个补充坑:只存聚合值不存直方图(双峰变单峰看不出)、💀基线只用"上周同期"(每周漂 2% 永远不告警,一年漂 65% → 必须同时看训练集基线和上周基线)、监控用采样日志但没记采样率。成本账:全量特征日志 TB/天,⭐按小时聚合+分位数只要 MB/天——聚合指标全量算负责发现,原始特征抽样 1% 落盘负责查因

下一节 👉 06-数据漂移与概念漂移.md

打卡记录保存在你的浏览器里,首页能看到总进度