4. PSI监控体系架构:从数据采集到告警链路的完整设计

一个生产级的因子PSI监控系统,不能仅停留在单次计算脚本的层面,而需要构建一条从数据采集、计算、存储到告警的完整链路。本章将拆解这一架构的每一层,并给出可直接落地的组件选型与交互流程。

4.1 架构总览:五层模型

我们将PSI监控体系划分为五个核心层,每一层承担明确的职责,并通过标准接口进行数据传递。

层级核心职责典型组件
数据采集层从离线数仓或实时流中拉取因子快照与基准分布SparkSQL / Flink CDC / Airflow DAG
计算引擎层执行PSI、特征缺失率、分箱稳定性等统计计算PySpark / Pandas UDF / ClickHouse
存储层持久化因子分布快照、PSI时序、告警记录HDFS / MySQL / Redis / InfluxDB
告警决策层基于阈值规则或动态基线判断是否触发告警Prometheus AlertManager / 自研规则引擎
通知与展示层将告警推送到飞书/钉钉/邮件,并提供可视化看板Grafana / Feishu Bot / 自研Dashboard
架构原则:每一层之间通过消息队列(Kafka)或API解耦,避免单点故障扩散。计算层与存储层之间建议使用列式存储(Parquet + ClickHouse)以提升批量查询性能。

4.2 数据采集层:基准分布与监控分布的获取

PSI计算需要两个分布:基准分布(Reference Distribution)和当前分布(Current Distribution)。采集层需要解决三个关键问题:

  • 基准版本管理:通常以模型上线日、月度重训日或季度回顾日为基准快照时间点,将因子分箱后的计数存入基准表。
  • 监控频率:离线场景按天/周调度,实时场景按小时或分钟级滑动窗口采集。
  • 数据质量校验:采集时需检查空值率、异常值、分箱边界漂移,避免脏数据污染PSI结果。
# 伪代码:基准分布采集与存储
def collect_reference_distribution(factor_name, reference_date, bins):
    df = spark.sql(f"""
        SELECT 
            CASE 
                WHEN {factor_name} < {bins[0]} THEN 'bin_0'
                WHEN {factor_name} < {bins[1]} THEN 'bin_1'
                ...
                ELSE 'bin_n'
            END AS bin_label,
            COUNT(*) AS cnt
        FROM model_features
        WHERE dt = '{reference_date}'
        GROUP BY bin_label
    """)
    df.write.mode("overwrite").parquet(f"/data/psi_ref/{factor_name}/{reference_date}")
    return df

4.3 计算引擎层:PSI与辅助指标的计算

计算层不仅计算PSI,还需同步计算以下辅助指标,用于告警降噪:

  • 分箱覆盖率:每个分箱的样本占比,防止某个分箱样本过少导致PSI虚高。
  • 缺失率变化:当前分布与基准分布的缺失率差值,缺失率突变往往先于PSI恶化。
  • 分箱漂移方向:记录每个分箱是正向偏移还是负向偏移,辅助归因。
指标名称计算公式告警阈值参考
PSIΣ (P_i - Q_i) * ln(P_i / Q_i)> 0.1 轻度漂移;> 0.25 严重漂移
缺失率差值|missing_rate_current - missing_rate_ref|> 0.05
最大分箱偏移max(|P_i - Q_i|)> 0.15
避坑提示:当某个分箱在基准分布中占比为0时,ln(P_i / Q_i) 会除零。建议对零频分箱做拉普拉斯平滑(加1平滑),或直接跳过该分箱并在日志中记录。

4.4 存储层:时序化与多版本管理

存储层需要支持两种查询模式:

  • 按因子查询PSI时序:用于Grafana绘制趋势图,推荐使用InfluxDB或ClickHouse的时序表引擎。
  • 按时间点回溯分布快照:用于事后归因分析,推荐将分箱明细以Parquet格式存储在HDFS,元数据写入MySQL。
-- ClickHouse 建表示例:因子PSI时序表
CREATE TABLE psi_monitor.psi_series (
    factor_name String,
    monitor_date Date,
    psi_value Float64,
    missing_rate_diff Float64,
    max_bin_shift Float64,
    total_sample_cnt UInt64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(monitor_date)
ORDER BY (factor_name, monitor_date);

4.5 告警决策层:阈值规则与动态基线

告警决策层是避免告警风暴的关键。我们采用分级告警策略:

  • P0(严重):PSI > 0.25 且 缺失率差值 > 0.1,立即通知模型负责人与风控值班。
  • P1(警告):PSI > 0.1 且 连续3天上升趋势,发送飞书群消息。
  • P2(提示):PSI > 0.05 但未达P1标准,仅记录日志,不主动推送。
动态基线优化:对于周期性明显的因子(如周末与工作日分布不同),建议使用过去7天或28天的滑动窗口计算动态阈值,而非固定0.1。例如:threshold = mean(psi_last_7d) + 2 * std(psi_last_7d)。

4.6 通知与展示层:多通道触达与可视化

通知层需要支持多渠道、可配置的告警路由:

  • 飞书/钉钉机器人:发送卡片消息,包含因子名称、当前PSI值、趋势图链接。
  • 邮件:发送日报/周报汇总,包含Top-N漂移因子列表。
  • Grafana看板:展示所有因子的PSI热力图,支持按业务线、模型版本筛选。

4.7 完整链路SVG架构图

下图展示了从数据采集到告警通知的完整数据流与组件交互关系:

PSI监控系统架构图 PSI监控系统架构图(五层链路) 数据采集层 SparkSQL 定时任务 | Flink CDC 实时采集 | Airflow DAG 调度 计算引擎层 PySpark PSI计算 | 分箱覆盖率 | 缺失率差值 | 最大分箱偏移 存储层 ClickHouse 时序表 | HDFS Parquet 快照 | MySQL 元数据 | Redis 缓存 告警决策层 P0/P1/P2 分级规则 | 动态基线 | 连续趋势检测 | 告警抑制 通知与展示层 飞书/钉钉机器人 | 邮件日报 | Grafana看板 数据流方向:采集 → 计算 → 存储 → 决策 → 通知,各层通过Kafka/API解耦

4.8 架构落地检查清单

在将上述架构部署到生产环境前,建议逐项确认以下要点:

  • 数据采集层:是否已处理基准分布版本过期问题?是否对实时流做了背压控制?
  • 计算引擎层:PSI计算是否支持增量更新(仅计算新增分箱)?是否记录了计算耗时用于性能监控?
  • 存储层:时序表是否设置了合理的TTL(数据保留周期)?分箱快照是否做了压缩?
  • 告警决策层:是否配置了告警静默期(避免重复告警)?动态基线是否在节假日自动调整?
  • 通知层:是否支持告警升级(P0未确认则自动电话通知)?是否提供了告警自助屏蔽入口?
最佳实践:建议在架构上线初期,先对所有因子开启“静默模式”(只记录告警日志不推送),运行一周后根据实际告警频率调整阈值,再逐步开放推送。这能有效避免初期阈值不合理导致的告警疲劳。